题外话: 一天肝三篇文章,爽啊。。。
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)
- 算法稳定性:
- 不稳定。
结束语:
堆排序的代码,还是需要自己默写一遍,才能理解其意义。
其中最重要的就是“堆调整”那个函数。
该函数既能建立一个堆,又能调整一个堆。
说白了,建立一个堆 = 从最后一个非叶子结点,向前,逐个结点进行“堆调整”。