排序进阶
插入排序
#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;
}
//第二步:
//递归平分数组
//结束条件为长度小于2
static 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];
else
right[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