排序进阶
文章目录
- 插入排序
- 希尔排序
- 归并排序
- 快速排序
- 堆排序
插入排序
#region 知识点一 插入排序的基本原理
// 8 7 1 5 4 2 6 3 9
// 两个区域
// 排序区
// 未排序区
// 用一个索引值做分水岭// 未排序区元素
// 与排序区元素比较
// 插入到合适位置
// 直到未排序区清空
#endregion#region 知识点二 代码实现
//实现升序 把 大的 放在最后面
int[] arr = new int[] { 8, 7, 1, 5, 4, 2, 6, 3, 9 };//前提规则
//排序开始前
//首先认为第一个元素在排序区中
//其它所有元素在未排序区中//排序开始后
//每次将未排序区第一个元素取出用于和
//排序区中元素比较(从后往前)
//满足条件(较大或者较小)
//则排序区中元素往后移动一个位置。//注意
//所有数字都在一个数组中
//所谓的两个区域是一个分水岭索引//第一步
//能取出未排序区的所有元素进行比较
//i=1的原因:默认第一个元素就在排序区
for (int i = 1; i < arr.Length; i++)
{//第二步//每一轮//1.取出排序区的最后一个元素索引int sortIndex = i - 1;//2.取出未排序区的第一个元素int noSortNum = arr[i];//第三步//在未排序区进行比较//移动位置//确定插入索引//循环停止的条件//1.发现排序区中所有元素都已经比较完//2.发现排序区中的元素不满足比较条件了while (sortIndex >= 0 &&arr[sortIndex] > noSortNum){//只要进了这个while循环 证明满足条件//排序区中的元素 就应该往后退一格arr[sortIndex + 1] = arr[sortIndex];//移动到排序区的前一个位置 准备继续比较--sortIndex;}//最终插入数字//循环中知识在确定位置 和找最终的插入位置//最终插入对应位置 应该循环结束后arr[sortIndex + 1] = noSortNum;
}for (int i = 0; i < arr.Length; i++)
{Console.WriteLine(arr[i]);
}
#endregion#region 知识点三 总结//为什么有两层循环
//第一层循环:一次取出未排序区的元素进行排序
//第二层循环:找到想要插入的位置//为什么第一层循环从1开始遍历
//插入排序的关键是分两个区域
//已排序区 和 未排序区
//默认第一个元素在已排序区//为什么使用while循环
//满足条件才比较
//否则证明插入位置已确定
//不需要继续循环//为什么可以直接往后移位置
//每轮未排序数已记录
//最后一个位置不怕丢//为什么确定位置后,是放在sortIndex + 1的位置
//当循环停止时,插入位置应该是停止循环的索引加1处//基本原理
//两个区域
//用索引值来区分
//未排序区与排序区
//元素不停比较
//找到合适位置
//插入当前元素//套路写法
//两层循环
//一层获取未排序区元素
//一层找到合适插入位置//注意事项
//默认开头已排序
//第二层循环外插入#endregion
希尔排序
#region 知识点一 希尔排序的基本原理
//希尔排序是
//插入排序的升级版
//必须先掌握插入排序//希尔排序的原理
//将整个待排序序列
//分割成为若干子序列
//分别进行插入排序//总而言之
//希尔排序对插入排序的升级主要就是加入了一个步长的概念
//通过步长每次可以把原序列分为多个子序列
//对子序列进行插入排序
//在极限情况下可以有效降低普通插入排序的时间复杂度
//提升算法效率
#endregion#region 知识点二 代码实现
int[] arr = new int[] { 8, 7, 1, 5, 4, 2, 6, 3, 9 };
//学习希尔排序的前提条件
//先掌握插入排序
//第一步:实现插入排序
//第一层循环 是用来取出未排序区中的元素的
//for (int i = 1; i < arr.Length; i++)
//{
// //得出未排序区的元素
// int noSortNum = arr[i];
// //得出排序区中最后一个元素索引
// int sortIndex = i - 1;
// //进入条件
// //首先排序区中还有可以比较的 >=0
// //排序区中元素 满足交换条件 升序就是排序区中元素要大于未排序区中元素
// while (sortIndex >= 0 &&
// arr[sortIndex] > noSortNum)
// {
// arr[sortIndex + 1] = arr[sortIndex];
// --sortIndex;
// }
// //找到位置过后 真正的插入 值
// arr[sortIndex + 1] = noSortNum;
//}//for (int i = 0; i < arr.Length; i++)
//{
// Console.WriteLine(arr[i]);
//}//第二步:确定步长
//基本规则:每次步长变化都是/2
//一开始步长 就是数组的长度/2
//之后每一次 都是在上一次的步长基础上/2
//结束条件是 步长 <=0
//1.第一次的步长是数组长度/2 所以:int step = arr.length/2
//2.之后每一次步长变化都是/2 索引:step /= 2
//3.最小步长是1 所以:step > 0
for (int step = arr.Length / 2; step > 0; step /= 2)
{//注意://每次得到步长后 会把该步长下所有序列都进行插入排序//第三步:执行插入排序//i=1代码 相当于 代表取出来的排序区的第一个元素//for (int i = 1; i < arr.Length; i++)//i=step 相当于 代表取出来的排序区的第一个元素for (int i = step; i < arr.Length; i++){//得出未排序区的元素int noSortNum = arr[i];//得出排序区中最后一个元素索引//int sortIndex = i - 1;//i-step 代表和子序列中 已排序区元素一一比较int sortIndex = i - step;//进入条件//首先排序区中还有可以比较的 >=0//排序区中元素 满足交换条件 升序就是排序区中元素要大于未排序区中元素while (sortIndex >= 0 &&arr[sortIndex] > noSortNum){//arr[sortIndex + 1] = arr[sortIndex];// 代表移步长个位置 代表子序列中的下一个位置arr[sortIndex + step] = arr[sortIndex];//--sortIndex;//一个步长单位之间的比较sortIndex -= step;}//找到位置过后 真正的插入 值//arr[sortIndex + 1] = noSortNum;//现在是加步长个单位arr[sortIndex + step] = noSortNum;}
}for (int i = 0; i < arr.Length; i++)
{Console.WriteLine(arr[i]);
}#endregion#region 知识点三 总结
//基本原理
//设置步长
//步长不停缩小
//到1排序后结束//具体排序方式
//插入排序原理//套路写法
//三层循环
//一层获取步长
//一层获取未排序区元素
//一层找到合适位置插入//注意事项
//步长确定后
//会将所有子序列进行插入排序
#endregion
归并排序
int[] arr = new int[] { 8, 7, 1, 5, 4, 2, 6, 3, 9 };arr = Merge(arr);for (int i = 0; i < arr.Length; i++)
{Console.WriteLine(arr[i]);
}#region 知识点一 归并排序基本原理
//归并 = 递归 + 合并//数组分左右
//左右元素相比较
//满足条件放入新数组
//一侧用完放对面//递归不停分
//分完再排序
//排序结束往上走
//边走边合并
//走到头顶出结果//归并排序分成两部分
//1.基本排序规则
//2.递归平分数组//递归平分数组:
//不停进行分割
//长度小于2停止
//开始比较
//一层一层向上比//基本排序规则:
//左右元素进行比较
//依次放入新数组中
//一侧没有了另一侧直接放入新数组
#endregion#region 知识点二 代码实现//第一步:
//基本排序规则
//左右元素相比较
//满足条件放进去
//一侧用完直接放
static int[] Sort(int[] left, int[] right)
{//先准备一个新数组int[] array = new int[left.Length + right.Length];int leftIndex = 0;//左数组索引int rightIndex = 0;//右数组索引//最终目的是要填满这个新数组//不会出现两侧都放完还在进循环 //因为这个新数组的长度 是根据左右两个数组长度计算出来的for (int i = 0; i < array.Length; i++){//左侧放完了 直接放对面右侧if (leftIndex >= left.Length){array[i] = right[rightIndex];//已经放入了一个右侧元素进入新数组//所以 标识应该指向下一个嘛rightIndex++;}//右侧放完了 直接放对面左侧else if (rightIndex >= right.Length){array[i] = left[leftIndex];//已经放入了一个左侧元素进入新数组//所以 标识应该指向下一个嘛leftIndex++;}else if (left[leftIndex] < right[rightIndex]){array[i] = left[leftIndex];//已经放入了一个左侧元素进入新数组//所以 标识应该指向下一个嘛leftIndex++;}else{array[i] = right[rightIndex];//已经放入了一个右侧元素进入新数组//所以 标识应该指向下一个嘛rightIndex++;}}//得到了新数组 直接返回出去return array;
}//第二步:
//递归平分数组
//结束条件为长度小于2static int[] Merge(int[] array)
{//递归结束条件if (array.Length < 2)return array;//1.数组分两段 得到一个中间索引int mid = array.Length / 2;//2.初始化左右数组//左数组int[] left = new int[mid];//右数组int[] right = new int[array.Length - mid];//左右初始化内容for (int i = 0; i < array.Length; i++){if (i < mid)left[i] = array[i];elseright[i - mid] = array[i];}//3.递归再分再排序return Sort(Merge(left), Merge(right));
}
#endregion#region 知识点三 总结
//理解递归逻辑
//一开始不会执行Sort函数的
//要先找到最小容量数组时
//才会回头递归调用Sort进行排序//基本原理
// 归并 = 递归 + 合并
// 数组分左右
// 左右元素相比较
// 一侧用完放对面
// 不停放入新数组//递归不停分
//分完再排序
//排序结束往上走
//边走边合并
//走到头顶出结果//套路写法
//两个函数
//一个基本排序规则
//一个递归平分数组//注意事项
//排序规则函数 在 平分数组函数
//内部 return调用#endregion
快速排序
int[] arr = new int[] { 8, 7, 1, 5, 4, 2, 6, 3, 9 };
QuickSort(arr, 0, arr.Length - 1);for (int i = 0; i < arr.Length; i++)
{Console.WriteLine(arr[i]);
}#region 知识点一 快速排序基本原理
//选取基准
//产生左右标识
//左右比基准
//满足则换位//排完一次
//基准定位//左右递归
//直到有序
#endregion#region 知识点二 代码实现
//第一步:
//申明用于快速排序的函数
static void QuickSort(int[] array, int left, int right)
{//第七步://递归函数结束条件if (left >= right)return;//第二步://记录基准值//左游标//右游标int tempLeft, tempRight, temp;temp = array[left];tempLeft = left;tempRight = right;//第三步://核心交换逻辑//左右游标会不同变化 要不相同时才能继续变化while (tempLeft != tempRight){//第四步:比较位置交换//首先从右边开始 比较 看值有没有资格放到表示的右侧while (tempLeft < tempRight &&array[tempRight] > temp){tempRight--;}//移动结束证明可以换位置array[tempLeft] = array[tempRight];//上面是移动右侧游标//接着移动完右侧游标 就要来移动左侧游标while (tempLeft < tempRight &&array[tempLeft] < temp){tempLeft++;}//移动结束证明可以换位置array[tempRight] = array[tempLeft];}//第五步:放置基准值//跳出循环后 把基准值放在中间位置//此时tempRight和tempLeft一定是相等的array[tempRight] = temp;//第六步://递归继续QuickSort(array, left, tempRight - 1);QuickSort(array, tempLeft + 1, right);
}
#endregion#region 知识点三 总结
//归并排序和快速排序都会用到递归
//两者的区别
//相同点:
//1.他们都会用到递归
//2.都会把数组分成几部分
//不同点:
//1.归并排序递归过程中会不停产生新数组用于合并;快速排序不会产生新数组
//2.归并排序是拆分数组完毕后再进行排序;快速排序是边排序边拆分//基本原理
//选取基准
//产生左右标识
//左右比基准
//满足则换位
//排完一次 基准定位
//基准左右递归
//直到有序//套路写法
//基准值变量
//左右游标记录//3层while循环
//游标不停左右移动
//重合则结束
//结束定基准//递归排左右
//错位则结束//注意事项
//左右互放
//while循环外定基准
#endregion
堆排序
int[] arr = new int[] { 8, 7, 1, 5, 4, 2, 6, 3, 9 };
HeapSort(arr);
for (int i = 0; i < arr.Length; i++)
{Console.WriteLine(arr[i]);
}#region 知识点一 堆排序基本原理
//构建二叉树
//大堆顶调整
//堆顶往后方
//不停变堆顶//关键规则
//最大非叶子节点:
//数组长度/2 - 1//父节点和叶子节点:
//父节点为i
//左节点2i+1
//右节点2i+2
#endregion#region 知识点二 代码实现
//第一步:实现父节点和左右节点比较
/// <summary>
///
/// </summary>
/// <param name="array">需要排序的数组</param>
/// <param name="nowIndex">当前作为根节点的索引</param>
/// <param name="arrayLength">哪些位置没有确定</param>
static void HeapCompare(int[] array, int nowIndex, int arrayLength)
{//通过传入的索引 得到它对应的左右叶子节点的索引//可能算出来的会溢出数组的索引 我们一会再判断int left = 2 * nowIndex + 1;int right = 2 * nowIndex + 2;//用于记录较大数的索引int biggerIndex = nowIndex;//先比左 再比右//不能溢出 if (left < arrayLength && array[left] > array[biggerIndex]){//认为目前最大的是左节点 记录索引biggerIndex = left;}//比较右节点if (right < arrayLength && array[right] > array[biggerIndex]){biggerIndex = right;}//如果比较过后 发现最大索引发生变化了 那就以为这要换位置了if (biggerIndex != nowIndex){int temp = array[nowIndex];array[nowIndex] = array[biggerIndex];array[biggerIndex] = temp;//通过递归 看是否影响了叶子节点他们的三角关系HeapCompare(array, biggerIndex, arrayLength);}
}//第二步:构建大堆顶
static void BuildBigHeap(int[] array)
{//从最大的非叶子节点索引 开始 不停的往前 去构建大堆顶for (int i = array.Length / 2 - 1; i >= 0; i--){HeapCompare(array, i, array.Length);}
}//第三步:结合大堆顶和节点比较 实现堆排序 把堆顶不停往后移动
static void HeapSort(int[] array)
{//构建大堆顶BuildBigHeap(array);//执行过后//最大的数肯定就在最上层//往屁股后面放 得到 屁股后面最后一个索引for (int i = array.Length - 1; i > 0; i--){//直接把 堆顶端的数 放到最后一个位置即可int temp = array[0];array[0] = array[i];array[i] = temp;//重新进行大堆顶调整HeapCompare(array, 0, i);}
}
#endregion#region 知识点三 总结
//基本原理
//构建二叉树
//大堆顶调整
//堆顶往后方
//不停变堆顶//套路写法
//3个函数
//1个堆顶比较
//1个构建大堆顶
//1个堆排序//重要规则
//最大非叶子节点索引:
//数组长度/2 - 1//父节点和叶子节点索引:
//父节点为i
//左节点2i+1
//右节点2i-1//注意:
//堆是一类特殊的树
//堆的通用特点就是父节点会大于或小于所有子节点
//我们并没有真正的把数组变成堆
//只是利用了堆的特点来解决排序问题
#endregion