C#【进阶】排序进阶

排序进阶

文章目录

插入排序

csharp 复制代码
#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

希尔排序

csharp 复制代码
#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

归并排序

csharp 复制代码
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

快速排序

csharp 复制代码
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

堆排序

csharp 复制代码
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
相关推荐
friklogff42 分钟前
【C#生态园】构建你的C#操作系统:框架选择与实践
服务器·开发语言·c#
马剑威(威哥爱编程)1 小时前
除了递归算法,要如何优化实现文件搜索功能
java·开发语言·算法·递归算法·威哥爱编程·memoization
算法萌新——12 小时前
洛谷P2240——贪心算法
算法·贪心算法
湖北二师的咸鱼2 小时前
专题:二叉树递归遍历
算法·深度优先
重生之我要进大厂2 小时前
LeetCode 876
java·开发语言·数据结构·算法·leetcode
KBDYD10103 小时前
C语言--结构体变量和数组的定义、初始化、赋值
c语言·开发语言·数据结构·算法
Crossoads3 小时前
【数据结构】排序算法---桶排序
c语言·开发语言·数据结构·算法·排序算法
自身就是太阳3 小时前
2024蓝桥杯省B好题分析
算法·职场和发展·蓝桥杯
code bean3 小时前
【C#基础】函数传参大总结
服务器·开发语言·c#
孙小二写代码4 小时前
[leetcode刷题]面试经典150题之1合并两个有序数组(简单)
算法·leetcode·面试