C# 选择排序 | 简单选择排序 | 堆排序 的原理和代码实现

题外话: 一天肝三篇文章,爽啊。。。


1.什么是选择排序?

  • 顾名思义,选择,选择,就是从数据中选一个出来放到有序序列中
  • 怎么选呢?
  • 如果从小到大排序,就从原始数据中选最小的,拿出来,放到结果数组中,然后从原始数据中删掉拿走的,如此循环

2.简单选择排序

  • 简单选择排序的思路是什么呢?
    • 就是最最最简单方法;
    • 选最小(大)的,拿出来,放一边
    • 再选剩下的数据中最小(大)的,拿出来,放一边
    • 循环这个步骤,就能得到一个有序序列
  • 代码实现
/// <summary>
/// 简单选择排序
/// </summary>
/// <param name="pArray"></param>
static public void SimpleSort(int[] pArray)
{
    
    
    int min, temp;
    //遍历每个元素,除了倒数第一个
    //因为第二层循环会从i + 1开始比较
    for (int i = 0; i < pArray.Length - 1; i++)
    {
    
    
        //找到最小值的那个索引
        min = i;
        for (int j = i + 1; j < pArray.Length; j++)
        {
    
    
            if (pArray[min] > pArray[j])
            {
    
    
                min = j;
            }
        }
        //最小值索引变了,就交换两者的位置
        if (min != i)
        {
    
    
            temp = pArray[i];
            pArray[i] = pArray[min];
            pArray[min] = temp;
        }
    }
}

  • 时间复杂度
    • 任何情况:O(n^2)
    • 因为无论原始数据是否有序,你还是需要一个一个比较大小的。
  • 空间复杂度:
    • O(1)
  • 算法稳定性:
    • 不稳定。

3.堆排序

  • 什么是堆?
  • 举例子:
  • 大根堆:
    • 二叉树中每个结点的值,均大于其左右孩子的值
    • 即保证“根节点,一定是整棵树中的最大值。”
  • 小根堆:
    • 二叉树中每个结点的值,均小于其左右孩子的值
    • 即保证“根节点,一定是整棵树中的最小值。”
  • 所以,堆排序的思路是什么呢?
    • 构建一个堆(比如大根堆);
    • 堆顶拿下一个元素,放在一边,他肯定是最大值
    • 剩余的元素重新构成一个大根堆
    • 再从堆顶拿下一个元素,放一边;
    • 循环这个步骤,你会拿到一个有序序列
  • 代码实现
/// <summary>
/// 堆排序
/// </summary>
/// <param name="pArray">从索引1开始有效的数组</param>
static public void HeapSort(int[] pArray)
{
    
    
    int i;
    int n = pArray.Length - 1;
    //建立一个堆,需要从最后一个非叶子结点调整
    for (i = n / 2; i >= 1; i--)
    {
    
    
        HeapAdjust2(pArray, i, n);
    }
    //将堆顶元素替换到集合尾部,并用剩余的元素重新建立一个堆
    //循环这个操作,直至只剩一个元素,一定是最小的那个
    for (i = n; i > 1; i--)
    {
    
    
        int temp = pArray[1];
        pArray[1] = pArray[i];
        pArray[i] = temp;
        HeapAdjust2(pArray, 1, i - 1);
    }
}

static public void HeapAdjust(int[] pArray, int s, int m)
{
    
    
    //记录该子树的根
    int rc = pArray[s];
    //遍历该子树的所有结点,并把最大值放在堆顶
    //为什么能保证子孩子中一定有最大值,而不是在孙子结点中?
    //因为调整是从最后一个非叶子结点调整的,也就是说,当子树
    //深度大于2时,其根节点的孩子结点,一定是子孙节点中最大的两个值。
    for (int j = 2 * s; j <= m; j *= 2)
    {
    
    
        //从两个孩子中取最大的那个
        if (j < m && pArray[j] < pArray[j + 1])
        {
    
    
            ++j;
        }
        //根比最大的孩子还要大,就不用找了
        if (rc >= pArray[j])
        {
    
    
            break;
        }
        //把孩子的值给到根节点
        pArray[s] = pArray[j];
        s = j;
    }
    //把根节点的值给到替换的孩子
    pArray[s] = rc;
}

  • 时间复杂度
    • O(nlogn)
  • 空间复杂度:
    • O(1)
  • 算法稳定性:
    • 不稳定。


结束语:
堆排序的代码,还是需要自己默写一遍,才能理解其意义。
其中最重要的就是“堆调整”那个函数。
该函数既能建立一个堆,又能调整一个堆。
说白了,建立一个堆 = 从最后一个非叶子结点,向前,逐个结点进行“堆调整”。

猜你喜欢

转载自blog.csdn.net/Liyager/article/details/129209712
今日推荐