目录
0. 前言
众所周知,对于顺序表,以二分法查找一个数,算法时间复杂度为O(nlongn)
。因而,一次排好序,便可以节约很多查找的时间。
由此可见,排序算法尤为重要。以下介绍八大排序算法,都是从小到大排序。
1. 插入排序![](https://img-blog.csdnimg.cn/30f0fb97493b44d9b1bc475b3450ab06.png)
i从1
到n-1
,每次将第i位的数插入到前面已排好序的序列中。
插入的方法为:将i与i-1位置的数比较,i小则交换并i--,i大则停止插入。
// 插入排序
// 时间复杂度:O(N^2) -- 逆序
// 最好 O(N) -- 顺序有序 或 接近顺序有序
void InsertSort(int* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
//单趟排序
int end = i;
int tmp = a[end + 1];
while (end >= 0)
{
//5 1 3 7 8 2
if (tmp < a[end])
{
a[end + 1] = a[end];
--end;
}
else
break;
}
a[end + 1] = tmp;
}
}
2. 希尔排序
将以5 3 1为增量的序列依次进行插入排序。例如,增量为5时,对(a0,a5...)、(a1,a6...)、(a2,a7...)、(a3,a8...)、(a4,a9...)序列依次进行插入排序。
// 希尔排序
// 平均:O(N^1.3)
void ShellSort(int* a, int n)
{
// 1、gap > 1 预排序
// 2、gap == 1 直接插入排序
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = 0; i < n - gap; i++)
{
//8 2 6 5 7 4 3
//end end+gap
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
break;
}
a[end + gap] = tmp;
}
}
}
3. 冒泡排序
每次从0到i序列的数中挑取最大的数放在i位置,i从n-1
到0
。
挑取最大数的过程为:将j位置数与j+1位置比较,j大则交换,小不交换,j++。
// 冒泡排序
void BubbleSort(int* a, int n)
{
for (int i = 0; i < n; i++)
{
int exchange = 0;
for (int j = 1; j < n - i; j++)
{
if (a[j - 1] > a[j])
{
Swap(&a[j - 1], &a[j]);
exchange = 1;
}
}
//如果单趟走完,已经有序,那么就不需要再冒了
if (exchange == 0)
{
break;
}
}
}
4. 1快速排序(hoare版本)
遍历一次数组,以第一个数为标准,分成三部分,实现:第一个数在中间,小于它的数在它前面,大于它的数在它后面。然后对前部分和后部分递归该操作,递归终点为数组中个数为1。此处要注意的是,中间部分的数不需要再递归操作。
快排核心
遍历一遍数组,空间复杂度为O(1)
,将数组分为三部分的方法为:
- 1)取出第一个数下标暂存key,设定left和right分别指向数组头和尾
- 2)right先走,直到找到比第一个数小
- 3)然后left走,直到找到比key大的数
- 4) 将left和right位置的数字交换
- 其中,要时刻保持left<right 。
- 走到最后left和right会相遇,将key和left(或者right)交换即可
// 快速排序递归实现
// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{
int key = left;
while (left < right)
{
//右边找比key小的
while (left < right && a[right] >= key)
{
--right;
}
//左边找比key大的
while (left < right && a[left] <= key)
{
++left;
}
Swap(&a[right], &a[left]);
}
//跳出循环说明相遇了
Swap(&a[key], &a[left]);
return left;
}
4. 2快速排序(挖坑法)
定义两个指针left指向起始位置,right指向最后一个元素的位置
然后指定一个坑(pit),right寻找比key 小的数字
找到后将right的数据赋给pit,right成为一个坑
然后left寻找比key 大的数字,找到将left的数据赋给pit,left成为一个新坑,循环这个过程,直到right 与left 相遇,然后将key返回给那个坑(最终:key的左边都是比key小的数,key的右边都是比key大的数),然后进行递归操作。
// 快速排序挖坑法
int PartSort2(int* a, int left, int right)
{
int key = a[left];
//坑位
int pit = left;
while (left < right)
{
//右边先走,找小
while (left < right && a[right] >= key)
{
right--;
}
a[pit] = a[right];
pit = right;
//左边走,找大
while (left < right && a[left] <= key)
{
left++;
}
a[pit] = a[left];
pit = left;
}
a[pit] = key;
return pit;
}
4. 3快速排序(前后指针法)
定义两个指针,一前一后,pre(前)指向起始位置,cur(后)指向pre的后一个位置;
实现过程:cur找比keyi小的数,同时++pre != cur,那么就交换cur和pre的数++pre==cur,那么就++cur
直到cur走到right前一个位置,终止循环。最后++prev,交换pre和keyi的值。返回中间位置pre。最后再继续递归。
//三数取中(前中后)
int GetMidIndex(int* a, int left, int right)
{
int mid = left + (right - left) / 2;
if (a[left] < a[mid])
{
if (a[mid] < a[right])
{
return mid;
}
else if (a[left] > a[right])
{
return left;
}
else
return right;
}
else //a[left]>a[mid]
{
if (a[mid] > a[right])
{
return mid;
}
else if (a[right] > a[left])
{
return left;
}
else
return mid;
}
}
// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{
//当数字在有序或者接近有序的时候上面的方法效率会非常低,我们可以用 三数取中法 解决。
int midi = GetMidIndex(a, left, right);
Swap(&a[midi], &a[left]);
int keyi = left;
int pre = left;
int cur = left + 1;
//pre cur
//5 3 2 7 4
while (pre <= cur)
{
if (a[cur] < a[keyi] && a[++pre] != a[cur])
{
Swap(&a[cur], &a[pre]);
}
cur++;
}
Swap(&a[keyi], &a[pre]);
return pre;
}
4.4快速排序(非递归法)
用栈实现
先将数组的left和right入栈(left right)
然后分别出栈,作为数组的end和begin
调用三大快排法之一,取到keyi
从keyi分成两个区间[[begin, keyi-1] [keyi+1, end]
// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
{
ST st;
StackInit(&st);
StackPush(&st, left);
StackPush(&st, right);
while (!StackEmpty(&st))
{
int end = StackTop(&st);
StackPop(&st);
int begin = StackTop(&st);
StackPop(&st);
int keyi = PartSort3(a, begin, end);
//左区间 右区间
//[begin, keyi-1] [keyi+1,end]
if (begin < keyi - 1)
{
StackPush(&st, begin);
StackPush(&st, keyi - 1);
}
if (keyi + 1 < end)
{
StackPush(&st, keyi + 1);
StackPush(&st, end);
}
}
/*销毁栈*/
StackDestory(&st);
}
5. 选择排序
每次从0到n-1个数中选最小的数放在最前面,最大的数放在最后面。
选完之后left--
right++
// 选择排序
void SelectSort(int* a, int n)
{
int left = 0;
int right = n - 1;
while (left < right)
{
int mini = left;
int maxi = left;
//每次排完之后最小值在前面,最大值在后面,所以i要小于right
for (int i = left + 1; i <= right; i++)
{
if (a[i] < a[mini])
{
mini = i;
}
if (a[i] > a[maxi])
{
maxi = i;
}
}
Swap(&a[left], &a[mini]);
//9 1 5 3 4 2
//maxi mini
//交换之后1 9
//maxi mini
//小标出现重合,需要特殊处理
if (left == maxi)
{
maxi = mini;
}
Swap(&a[right], &a[maxi]);
left++;
right--;
}
}
6. 堆排序
堆排序,又称完全二叉树排序。是将数组看作一颗完全二叉树,数组i
位置的左右孩子分别为2*i+1
和2*i+2
位置。
排序过程为:
- 1)建堆:从最后一个往第一个,逐个进行“筛选”,即建成最大顶堆;
- 2)排序:将堆顶取出,与最后一个进行交换,然后再对第一个点进行“筛选”,此次“筛选”数组长度减一;
- 筛选:对某个结点筛选,即当该结点两个孩子中存在值比该结点大的,就交换位置。然后重复操作,直到结点的两个孩子的值都比该结点小,或孩子为空。
// 堆排序
void AdjustDwon(int* a, int n, int root)
{
int parent = root;
//默认左孩子
int child = parent * 2 + 1;
while (child < n)
{
//选出左右孩子中小的那一个
//a[child+1]>a[child] 建大堆
if (child + 1 < n && a[child + 1] < a[child])
{
child++;
}
//如果孩子比父亲小,则交换,继续向下调整
//a[child]>a[parent] 建大堆
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapSort(int* a, int n)
{
// 向上调整--建堆 O(N*logN)
//for (int i = 1; i < n; ++i)
//{
// AdjustUp(a, i);
//}
// 向下调整--建堆 O(N)
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
AdjustDown(a, n, i);
}
size_t end = n - 1;
while (end > 0)
{
//首尾交换
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
--end;
}
}
7. 1 归并排序(递归实现)
对于一段数组先分两半,各自进行归并排序,再将两半部分内容有序地合并起来,此时只需各遍历一次即可,采用递归实现。递归的终结点是数组中只有一个数时则不需要进行上述操作。
void _MergeSort(int* a, int begin, int end, int* tmp)
{
if (begin >= end)
return;
int mid = begin + (end - begin) / 2;
//[begin,mid] [mid+1,end]
_MergeSort(a, begin, mid, tmp);
_MergeSort(a, mid + 1, end, tmp);
//归并
int begin1 = begin;
int end1 = mid;
int begin2 = mid + 1;
int end2 = end;
int tmpi = begin;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[i++] = a[begin1++];
}
else
{
tmp[i++] = a[begin2++];
}
}
while (begin1 <= end1)
tmp[i++] = a[begin1++];
while (begin2 <= end2)
tmp[i++] = a[begin2++];
//将tmp内容拷贝到原数组中
memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}
// 归并排序递归实现
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
assert(tmp);
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
}
7. 2 归并排序(非递归实现)
将数组分为gap组,每次gap组内先排序然后再合并
通过拷贝到tmp数组内然后再拷贝回原数组
// 归并排序非递归实现
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
//组
int gap = 1;
while (gap < n)
{
//间距为gap是一组,两两归并
for (int i = 0; i < n; i += gap * 2)
{
int begin1 = i;
int end1 = i + gap - 1;
int begin2 = i + gap;
int end2 = i + 2 * gap - 1;
//存在越界的情况,含有gap的都有可能越界
//end1越界
if (end1 > n)
{
end1 = n - 1;
}
//begin2 越界,说明第二个区间不存在
if (begin2 > n)
{
//begin2>end2
begin2 = n;
end2 = n - 1;
}
//begin2 存在但end2越界
if (begin2 < n && end2 >= n)
{
end2 = n - 1;
}
int tmpi = begin;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[i++] = a[begin1++];
}
else
{
tmp[i++] = a[begin2++];
}
}
while (begin1 <= end1)
tmp[i++] = a[begin1++];
while (begin2 <= end2)
tmp[i++] = a[begin2++];
}
memcpy(a, tmp, n * sizeof(int));
gap *= 2;
}
free(tmp);
}
8. 计数排序
在所有要排序的数据中找到最大的数,求出它的位数n。
建立编号从0到9的十个队列。
i从最高位n到最低位1。
遍历数组将第i位为0的依次放入编号为0的队列中,第i位为1的放入编号为1的队列中。。。
所有数入相应队列后,再依次将0到9编号队列中数取出放回数组中。
然后对低一位重复该操作。
// 计数排序
// 6 5 6 7 4 5 10
// 2 2 1 1 1
void CountSort(int* a, int n)
{
int min = a[0];
int max = a[0];
for (int i = 0; i < n; i++)
{
if (a[i] < min)
min = a[i];
if (a[i] > max)
max = a[i];
}
int range = max - min - 1;
int* count = (int*)malloc(sizeof(int) * range);
assert(count);
//初始化数组
memset(count, 0, sizeof(int) * range);
//计数
for (int i = 0; i < n; i++)
{
count[a[i] - min]++;
}
int j = 0;
for (int i = 0; i < range; i++)
{
while (count[i]--)
{
a[j++] = i + min;
}
}
}
9.八大排序时间复杂度