1. 算法概述
1.1. 算法分类
几种常见排序算法可以分为两大类:
比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。
1.2 算法复杂度
1.3 相关概念
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
2. 算法详解
2.1. 冒泡排序(Bubble Sort)
参考:https://www.cnblogs.com/onepixel/articles/7674659.html
void bubbleSort(int *nums, int numsSize)
{
for(int i = 0; i < numsSize - 1; ++i)
{
int flag = 0;
for(int j = 0; j < numsSize - 1 - i; ++j)
{
if(nums[j] > nums[j + 1])
{
flag = 1;
int tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
}
}
if(0 == flag)
{
break;
}
}
}
2.2. 选择排序(Selection Sort)
参考:https://www.cnblogs.com/onepixel/articles/7674659.html
void selectionSort(int *nums, int numsSize)
{
for(int i = 0; i < numsSize - 1; ++i)
{
int minIndex = i;
for(int j = i + 1; j < numsSize; ++j)
{
if(nums[j] < nums[minIndex])
{
minIndex = j;
}
}
int tmp = nums[i];
nums[i] = nums[minIndex];
nums[minIndex] = tmp;
}
}
2.3 插入排序(Insertion Sort)
参考:https://www.cnblogs.com/onepixel/articles/7674659.html
void insertionSort(int *nums, int numsSize)
{
for(int i = 1; i < numsSize; ++i)
{
int preIndex = i - 1;
int current = nums[i];
while(preIndex >= 0 && nums[preIndex] > current)
{
nums[preIndex + 1] = nums[preIndex];
--preIndex;
}
nums[preIndex + 1] = current;
}
}
2.4. 希尔排序(Shell Sort)
参考:https://www.cnblogs.com/onepixel/articles/7674659.html
void shellSort(int *nums, int numsSize)//子序列交叉使用插入排序
{
for(int gap = numsSize / 2; gap > 0; gap /= 2)
{
for(int i = gap; i < numsSize; ++i)
{
int j = i;
int current = nums[i];
while(j - gap >= 0 && current < nums[j - gap])
{
nums[j] = nums[j - gap];
j -= gap;
}
nums[j] = current;
}
}
}
参考:https://www.runoob.com/w3cnote/sort-algorithm-summary.html
void shellSort1(int * nums, int numsSize)//一个子序列插入排序完成后,才轮到另一个子序列
{
for(int gap = numsSize / 2; gap > 0; gap /= 2)
{
for(int k = 0; k < gap; ++k)
{
for(int i = k + gap; i < numsSize; i += gap)
{
int j = i;
int current = nums[i];
while(j - gap >= k && current < nums[j - gap])
{
nums[j] = nums[j - gap];
j -= gap;
}
nums[j] = current;
}
}
}
}
2.5. 归并排序(Merge Sort)
参考:https://www.cnblogs.com/eniac12/p/5329396.html#s4
void mergeArray(int *nums, int first, int middle, int last)//合并有序数组
{
int nums1First = first;
int nums1Last = middle;
int nums2First = middle + 1;
int nums2Last = last;
int *tmpNums = new int[last - first + 1];
int k = 0;
while(nums1First <= nums1Last && nums2First <= nums2Last)
{
if(nums[nums1First] < nums[nums2First])
{
tmpNums[k] = nums[nums1First];
++k;
++nums1First;
}
else
{
tmpNums[k] = nums[nums2First];
++k;
++nums2First;
}
}
while(nums1First <= nums1Last)
{
tmpNums[k] = nums[nums1First];
++k;
++nums1First;
}
while(nums2First <= nums2Last)
{
tmpNums[k] = nums[nums2First];
++k;
++nums2First;
}
for(int i = 0; i < k; ++i)
{
nums[first + i] = tmpNums[i];
}
delete [] tmpNums;
}
void mergeSort(int * nums, int first, int last)//递归
{
if(first < last)
{
int middle = (first + last) / 2;
mergeSort(nums, first, middle);
mergeSort(nums, middle + 1, last);
mergeArray(nums, first, middle, last);
}
}
void mergeSort1(int * nums, int numsSize)//非递归
{
int first, middle, last;
for(int i = 1; i < numsSize; i *= 2)
{
first = 0;
while(first + i < numsSize)
{
middle = first + i - 1;
last = middle + i < numsSize ? middle + i : numsSize - 1;
mergeArray(nums, first, middle, last);
first = last + 1;
}
}
}
2.6. 快速排序(Quick Sort)
参考:https://www.runoob.com/w3cnote/quick-sort.html
void quickSort(int *nums, int first, int last)
{
if(first >= last)
return;
int i = first;
int j = last;
int key = nums[first];
while(i < j)
{
while(i < j && nums[j] >= key)
--j;
if(i < j)
nums[i++] = nums[j];
while(i < j && nums[i] < key)
++i;
if(i < j)
nums[j--] = nums[i];
}
// i == j
nums[i] = key;
quickSort(nums, first, i - 1);
quickSort(nums, i + 1, last);
}
2… 堆排序(Heap Sort)
堆排序是利用堆的性质进行的一种选择排序。下面先讨论一下堆。
1.堆
堆实际上是一棵完全二叉树,其任何一非叶节点满足性质:
Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]或者Key[i]>=Key[2i+1]&&key>=key[2i+2]
即任何一非叶节点的关键字不大于或者不小于其左右孩子节点的关键字。
堆分为大顶堆和小顶堆,满足Key[i]>=Key[2i+1]&&key>=key[2i+2]称为大顶堆,满足 Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]称为小顶堆。由上述性质可知大顶堆的堆顶的关键字肯定是所有关键字中最大的,小顶堆的堆顶的关键字是所有关键字中最小的。
2.堆排序的思想
利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。
其基本思想为(大顶堆):
1)将初始待排序关键字序列(R1,R2…Rn)构建成大顶堆,此堆为初始的无序区;
2)将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,…Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,…Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2…Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
操作过程如下:
1)初始化堆:将R[1…n]构造为堆;
2)将当前无序区的堆顶元素R[1]同该区间的最后一个记录交换,然后将新的无序区调整为新的堆。
因此对于堆排序,最重要的两个操作就是构造初始堆和调整堆,其实构造初始堆事实上也是调整堆的过程,只不过构造初始堆是对所有的非叶节点都进行调整。
下面举例说明:
给定一个整形数组 nums[] = {4,1,3,9,0,6,5,8,2,7},对其进行堆排序。
首先根据该数组元素构建一个完全二叉树,然后需要构造初始堆,则从最后一个非叶节点开始调整,每次调整都是从父节点、左孩子节点、右孩子节点三者中选择最大者跟父节点进行交换(交换之后可能造成被交换的孩子节点不满足堆的性质,因此每次交换之后要重新对被交换的孩子节点进行调整),这样就得到了初始堆。有了初始堆之后就可以进行排序了。
堆排序其实也是一种选择排序,是一种树形选择排序。只不过直接选择排序中,为了从R[1…n]中选择最大记录,需比较n-1次,然后从R[1…n-2]中选择最大记录需比较n-2次。事实上这n-2次比较中有很多已经在前面的n-1次比较中已经做过,而树形选择排序恰好利用树形的特点保存了部分前面的比较结果,因此可以减少比较次数。对于n个关键字序列,最坏情况下每个节点需比较log2(n)次,因此其最坏情况下时间复杂度为nlogn。堆排序为不稳定排序,不适合记录较少的排序。
void minHeapAdjust(int * nums, int i, int numsSize)//非递归,小顶堆
{
int j = 2 * i + 1;
while(j < numsSize)
{
if(j + 1 < numsSize && nums[j + 1] < nums[j])
++j;
if(nums[i] <= nums[j])
break;
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
i = j;
j = 2 * i + 1;
}
}
void maxHeapAdjust(int *nums, int i, int numsSize)//递归,大顶堆
{
int leftChildIndex = 2 * i + 1;
int rightChildIndex = 2 * i + 2;
int maxIndex = i;
if(i <= (numsSize - 1) / 2)
{
if(leftChildIndex < numsSize && nums[leftChildIndex] > nums[maxIndex])
maxIndex = leftChildIndex;
if(rightChildIndex < numsSize && nums[rightChildIndex] > nums[maxIndex])
maxIndex = rightChildIndex;
if(maxIndex != i)
{
int tmp = nums[i];
nums[i] = nums[maxIndex];
nums[maxIndex] = tmp;
maxHeapAdjust(nums, maxIndex, numsSize);
}
}
}
void buildHeap(int * nums, int numsSize)
{
for(int i = (numsSize - 1) / 2; i >= 0; --i)
//minHeapAdjust(nums, i, numsSize);
maxHeapAdjust(nums, i, numsSize);
}
void heapSort(int *nums, int numsSize)
{
buildHeap(nums, numsSize);
for(int i = numsSize - 1; i > 0; --i)
{
int tmp = nums[0];
nums[0] = nums[i];
nums[i] = tmp;
//minHeapAdjust(nums, 0, i);
maxHeapAdjust(nums, 0, i);
}
}
3. 简单的测试程序
#include "stdafx.h"
void printNums(int * nums, int numsSize)
{
for(int i = 0; i < numsSize; ++i)
{
printf("%d ", nums[i]);
}
printf("\n");
}
int main(int argc, char* argv[])
{
int nums[10] = {4, 1, 3, 9, 0, 6, 5, 8, 2, 7};
printNums(nums, 10);
//bubbleSort(nums, 10);
//selectionSort(nums, 10);
//insertionSort(nums, 10);
//shellSort(nums, 10);
//shellSort1(nums, 10);
//mergeSort(nums, 0, 9);
//mergeSort1(nums, 10);
//quickSort(nums, 0, 9);
heapSort(nums, 10);
printNums(nums, 10);
return 0;
}