常用的排序算法有:
交换排序(冒泡排序、快速排序)
选择排序(直接选择排序、堆排序)
插入排序(直接插入排序、二分插入排序、希尔排序)
归并排序、
基数排序
直接选择排序和冒泡排序的比较
void bubblesort(vector<int>& list)
{
for (int i = 0; i < list.size(); i++)
for (int j = 0; j < list.size() - i; j++)
{
if (list[j] > list[j + 1])
swap(list[j], list[j + 1]);
}
}
void choosesort(vector<int>& list)
{
int min, minP;
for (int i = 0; i < list.size(); i++)
{
min = list[i];
for (int j = i; j < list.size(); j++)
{
if (min > list[j])
{
min = list[j];
minP = j;
}
}
swap(list[i], list[minP]);
}
}
两种排序方法的平均和最坏时间复杂度是一样的都是O(n2),冒泡排序的最好时间复杂度是O(n),直接选择排序的最好时间复杂度也是O(n2)。一般来说选择比冒泡效率高(单从时间复杂度来考虑是看不出来的),从上面代码可以看出冒泡排序的交换操作在内层,而选择排序在外循环中进行交换操作。冒泡的话如果不这么频繁交换则需要记录坐标(牺牲空间复杂度),还有一点就是可以检测整个数组是否已经有序,即当某次遍历没有发生任何交换的时候终止算法。
插入排序:就是每步将一个待排序的记录,按顺序插入到前面已经排好序的序列中。直接插入排序就是从后往前挨个比较,二分插入的话就是运用二分法将待排序记录插入。
void insertsort(vector<int> list)
{
//外层循环表示待排序记录
for (int i = 1; i < list.size(); i++)
for (int j = 0; j < i; j++)
{
//内层循环实现插入
if (list[j] > list[i])
swap(list[j], list[i]);
else
break;
}
}
int binary_search(const vector<int>& list, int start, int end, int list_i)
{
while (start <= end)
{
int middle = (start + end) / 2;
int middle_data = list[middle];
if (middle_data > list_i)
end = middle - 1;
else
start = middle + 1;
}
return start;
}
void b_insertsort(vector<int> list)
{
for (int i = 1; i < list.size(); i++)
{
if (list[i] < list[i - 1])
{
//得到插入位置的索引
int index = binary_search(list, 0, i, list[i]);
//移动元素,从index到i
for (int j = i - 1; j >= index; j--)
swap(list[j + 1], list[j]);
}
}
}
希尔排序是一种分组插入排序,通过将增量逐渐减小将逆序记录消除。
void shellsort(vector<int> list)
{
int j;
//设置初始步长
int num = list.size();
num /= 2;
vector<int> tmp;
while (num >= 1)
{
//先根据步长分组插入排序
for (int i = num; i < list.size(); i++)
{
//将list[i]插入同组已经排好序的序列
int tmp = list[i];
j = i - num;
//如果小往前比
while (j >= 0 && tmp < list[j])
{
list[j + num] = list[j];
j -= num;
}
list[j + num] = tmp;
}
num /= 2;
}
}
希尔排序是最先突破O(n2)的排序算法的一批,排序的本质是消除逆序数,冒泡、直接插入排序等每次只能消除一个,所以复杂度都是O(n2),而比较快的如快排、希尔、堆排都是交换比较远的记录,使得一次交换能够消除一个以上的逆序,只不过规则不同。
快速排序属于交换排序,是冒泡排序的一种改进。基本思想是通过一趟排序将要排序的记录分为两个部分,然后分别运用快排进行排序。从显式的感觉上看,快排的快的有道理的,快排有远距离交换,他每扫一遍待排序序列都会更有序,大的小的分开在两边。
源码:
//递归版
int sort(vector<int>& list, int low, int high)
{
//选首元素作为轴
int tmp = list[low];
while (low < high)
{
while (list[high] >= tmp)
high--;
list[low] = list[high];
while (list[low] <= tmp)
low++;
list[high] = list[low];
}
list[low] = tmp;
return low;
}
void quicksort(vector<int>& list, int low, int high)
{
int mid = sort(list, low, high);
quicksort(list, low, mid - 1);
quicksort(list, mid, high);
}
接下来到堆排序同志了,在这之前先介绍一下他的简配版----树形选择排序,也叫锦标赛排序,基本思想就是锦标赛的晋级机制(8进4、半决赛、决赛)。
首先将待排序记录两两比较,然后取较小的记录继续进行比较(这里以升序排列为例),这样就会得到一个有n个叶子节点的完全二叉树,如图:
然后开始第二阶段,输出根节点,即得到最终结果的第一个记录。这时从叶子节点找到和最小值相同的记录,把这个记录当成最大开始比较得到次小值,如图:(按比赛的思想来说就是找出被他打败的选手里哪个更强)
重复第二阶段可以完成排序。由于n个叶子节点的完全二叉树的深度是log2n+1,所以每选一次需要进行log2n次比较,n个点的时间复杂度就是nlog2n;但是这种方法需要的辅助存储空间多,而且有一些最大值多余的比较。为了做一下优化,堆排序就诞生了。
堆排序刚开始也是构建一个完全二叉树,只不过这里不会有重复记录了,即先按照初始序列自顶向下写出来就好了,然后按需求(升序还是降序)选择调整成最大堆还是最小堆,这里以降序构造最大堆为例进行说明。最小堆就是这棵树的所有父节点都比自己的子节点大。
构造最大堆是从最后一个非叶子节点开始,从右往左从下往上地调整。如图,初始序列[16,7,3,20,17,8]
从3开始,3-8不满足最大堆定义,交换。
7不满足,将7和他最大子节点交换。
16不满足,将16和20交换。
换完之后16不满足,16和17交换。
最大堆生成:
第二阶段:输出堆顶元素,将堆顶元素和最后一个元素交换,调整堆。
3和20交换,3/17/8不满足,选8和17中较大的和3交换,此时3/7/16又不满足了,继续交换得到图3,此时最大堆又出现了。
继续重复操作,完成排序:
堆排序源码:
//堆调整
void heapadjust(vector<int>& list, int s, int length)
{
//s为当前字数的临时堆顶,先暂存到temp
int maxTemp = s;
//s的左儿子2*s+1、右儿子2*s+2
int sLchild = 2 * s + 1;
int sRchild = 2 * s + 2;
if (s <= length / 2)
{
//如果左儿子比较大,调整
if (sLchild <= length && list[sLchild] > list[maxTemp])
{
maxTemp = sLchild;
}
//如果右儿子比较大,调整
if (sRchild <= length && list[sRchild] > list[maxTemp])
{
maxTemp = sRchild;
}
//如果有调整,交换list记录maxTemp,s
if (list[maxTemp] != list[s])
{
swap(list[maxTemp], list[s]);
heapadjust(list, maxTemp, length);
}
}
}
//初建堆
void buildheap(vector<int>& list)
{
//有n个节点的完全二叉树,最后一个非叶节点是n/2-1,i--则遍历了所有非叶节点,堆调整后变大顶堆
for (int i = list.size() / 2 - 1; i >= 0; i--)
{
heapadjust(list, i, list.size());
}
}
void heapsort(vector<int> list)
{
buildheap(list);
for (int i = list.size() - 1; i >= 1; i--)
{
//每次将堆顶记录和当前未排序的最后一个记录交换
swap(list[i], list[0]);
//调整新的大顶堆
heapadjust(list, 0, i);
}
}
还剩归并排序和基数排序,为了更好地阅读体验(其实是我有点乱了先写到这吧,哈哈)写到下一篇吧。
一定要看哦!!!