目录
一.插入类
1.直接插入排序
2.希尔排序
二.选择类,排一次选出一个最值
1.选择排序
2.堆排序
三.交换类,通过一直交换一次确定数字的位置
1.冒泡排序
2.快速排序
2.1 hoare版本
2.2挖坑法
2.3前后指针法
四.归并类
1.归并排序
一.插入类
1.直接插入排序
像整理扑克牌一样,把后面的乱序牌依次插入到前面有序的地方,使得整体有序。
步骤:
1.从第一个元素开始,认为有序范围为下标0-0
2.取有序范围的下一个元素,将该元素与有序范围里的元素不断比较交换,插入到该元素应该在的位置,有序范围加1
3.重复2直到有序范围是全体
// 插入排序
void InsertSort(int* a, int n)
{
for (int i = 1; i < n; i++)
{
for (int j = i; j > 0; j--)
{
if (a[j] < a[j - 1])
swap(&a[j], &a[j - 1]);
else
break;
}
}
}
void swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
时间复杂度:最坏情况下为O(N^2),此时待排序列为逆序,或者整体接近逆序
最好情况下为O(N),此时待排序列为升序,或者整体接近升序。
空间复杂度:O(1)
2.希尔排序
上述的直接插入排序算法时间复杂度是O(n^2),如果要令此排序算法的时间复杂度要低于O(n^2),必须减少交换的工作量。那通过什么减少交换的工作量呢?希尔排序可以解决这个问题。
希尔在直接排序的基础上对序列进行预处理,然后进行直接插入排序,目的是为了最后一步直接插入排序的时候可以减少交换次数,同时也减少时间上的消耗。预处理时是通过增加交换的两个数字的距离,使得大数字先到较后面避免交换次数过多。
假定数组初始状态:5,1,9,3,7,4,8,6,2
首先是预处理,设定初始增量是gap=length/2 =9/2=4,每两个元素之间比较和交换的距离都是4,数据被分成4组,【5,7,2】,【1,4】,【9,8】,【3,6】对这4组分别进行组内直接插入排序。
接着逐步缩小增量,gap=4/2=2,说明两个元素待会比较和交换的距离都是2,被分为两组,对这两组也进行排序。每次gap都缩为一半。
最后增量缩小为1,这时候就是纯正的直接插入排序了,预处理使得这整个序列大致有序,真正减少了交换的次数,也真正减少了时间上的消耗。
// 希尔排序
void ShellSort(int* a, int n)
{
for (int gap = n / 2; gap >= 1; gap /= 2)
{
//根据gap不同,处理一次
for (int i = 0; i < n; i++)
{
//每躺对下标为i的元素所属组进行处理,此时该组有序范围是:i-gap,i-2*gap,……,i-n*gap
//j控制两两比较的后面元素下标:i i-gap,……i-n*gap 范围大于gap
for (int j = i; j >= gap; j -= gap)
{
if (a[j] < a[j - gap])
{
swap(&a[j], &a[j - gap]);
}
else
break;
}
}
}
}
void swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
时间复杂度平均:O(N^1.3)
空间复杂度:O(1)
二.选择类,排一次选出一个最值
1.选择排序
步骤:
1.未排序列下标为0-n-1,每次从未排序列中选出一个最小值,然后放在未排序序列的起始位置,此时将未排序列下标范围缩小1
2.重复1,直到未排序列只有一个数
实际中,我们一般一趟选出两个值,一个最大值一个最小值,然后将其放在序列开头和末尾,一次缩小两个数字的范围,提高排序的效率。
// 选择排序
void SelectSort(int* a, int n)
{
int min = 0, max = 0,minindex=0,maxindex=0;
for (int i = 0,j=n-1; i<j; i++,j--)
{
min = a[i], max = a[i],minindex=i,minindex=i;
for (int k = i; k <= j; k++)
{
if (a[k] < min)
{
min = a[k], minindex = k;
}
if (a[k] > max)
{
max = a[k], maxindex = k;
}
}
swap(&a[i], &a[minindex]);
//交换后min到了前面,但是有可能最大值是最前面的数,maxindex就应该变动
if (i == maxindex)
{
maxindex = minindex;
}
swap(&a[j], &a[maxindex]);
}
}
void swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
时间复杂度:最坏情况:O(N^2)
最好情况:O(N^2)
空间复杂度:O(1)
2.堆排序
我们知道对于数据建立一个大(小)堆,跟节点就是最大(小)值,和选择算法一样,我们每次都取一个最值放到序列前面或者后面,未排序列下标范围缩小1。
步骤:
1.未排序列下标为0-n-1,每次对未排序列进行建堆,选出一个最小值,然后放在未排序序列的起始位置,此时将根节点删除调整堆,堆的个数减1,也就是没排序的数字个数减1
2.重复1,直到未排序列只有一个数
具体可以查看上篇文章二叉树
// 堆排序
void AdjustDown(int* p, int size, int index)
{
int parent = index;
int child = parent * 2 + 1;
//大堆
while (child < size)
{
if (child + 1 < size && p[child + 1] > p[child])
{
child++;
}
if (p[parent] > p[child])
{
break;
}
else
{
swap(&p[child], &p[parent]);
parent = child;
child = parent * 2 + 1;
}
}
}
void HeapSort(int* a, int n)
{
for (int i = (n - 2) / 2; i >= 0; i--)
{
AdjustDown(a, n, i);
}
while (n > 1)
{
swap(&a[0], &a[n - 1]);
n--;
AdjustDown(a, n, 0);
}
}
时间复杂度:O(N*log2N)
空间复杂度: O(1)
三.交换类,通过一直交换一次确定数字的位置
1.冒泡排序
步骤:
1.记未排序范围为0-n-1,在该范围两两交换,大的去后面,然后范围减1
2.重复直到范围里只有一个数
i控制范围的最右边下标,j为两两交换的后面一个数的下标
// 冒泡排序
void BubbleSort(int* a, int n)
{
for (int i = n-1; i >= 0; i--)
{
int flag = 1;
for (int j = 1; j <= i; j++)
{
if (a[j] < a[j - 1])
{
swap(&a[j], &a[j - 1]);
flag = 0;
}
}
if (flag == 1)
break;
}
}
时间复杂度:最坏情况:O(N^2)
最好情况:O(N)
空间复杂度:O(1)
2.快速排序
基本思想:在序列里任意选择一个数字key(一般选第一个),通过一趟排序将要排序的数据分割成独立的两部分,其中第一部分的所有数据都比key都要小,第二部分的所有数据都比key都要大。如图:
这样就将问题转换成排好第一部分和第二部分的子序列的子问题。再对第一部分和第二部分数据分别进行同样的快排,整个问题就解决了。一般可以通过递归和非递归两种方法实现。
递归
整个排序过程可以递归进行,直到key两边数据只有1个或者0个。伪代码:
// 快速排序递归实现
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
int keyi = PartSort1(a, left, right);
//PartSort2(a, left, right);
//PartSort3(a, left, right);
QuickSort(a, left, keyi - 1);
QuickSort(a, keyi + 1,right);
}
单次排序PartSort函数将数据分成三段如何实现?一般有三种实现方式:
2.1 hoare版本
// 单次排序hoare版本
int PartSort1(int* a, int left, int right)
{
int key = left;
int p2 = right;
//不能+1, 数据:2 3 4 5 p1==p2==1 2与3会交换
int p1 = left;
while (p1 < p2)
{
//相等的数也跳过,没必要交换
while (p2 > p1 && a[p2] >= a[key])
{
p2--;
}
while (p2 > p1 && a[p1] <= a[key])
{
p1++;
}
swap(&a[p1], &a[p2]);
}
swap(&a[key], &a[p1]);
return p1;
}
2.2挖坑法
// 单次挖坑法
int PartSort2(int* a, int left, int right)
{
int key = a[left];
int hole = left;
int p1 = left, p2 = right;
while (p1 < p2)
{
while (p2 > p1 && a[p2] >= key)
{
p2--;
}
a[hole] = a[p2];
hole = p2;
while (p2 > p1 && a[p1] <= key)
{
p1++;
}
a[hole] = a[p1];
hole = p1;
}
a[hole] = key;
return hole;
}
2.3前后指针法
基本步骤:
首先确定两个指针位置和key值,key值一般选择最左边的。
接着cur遍历所有值。
遍历完成后,最后一步,将key与prev交换
// 单次前后指针法
int PartSort3(int* a, int left, int right)
{
int key = a[left];
int pre = left;
int cur = left + 1;
while (cur <= right)
{
if (a[cur] < key&&++pre != cur)
swap(&a[cur], &a[pre]);
cur++;
}
swap(&a[pre], &a[left]);
return pre;
}
非递归
基本思路是将由递归完成的子区间的单词排序由栈实现,由栈记录来每次自区间左右的下标,再循环对栈内区间单次排序处理 。
//快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
{
Stack st;
StackInit(&st);
StackPush(&st, right);
StackPush(&st, left);
while (!Stackempty(&st))
{
int left = StackTop(&st);
StackPop(&st);
int right = StackTop(&st);
StackPop(&st);
int keyi=PartSort3(a,left, right);
if (keyi + 1 < right)
{
StackPush(&st, right);
StackPush(&st, keyi + 1);
}
if (keyi - 1 > left)
{
StackPush(&st, keyi-1);
StackPush(&st, left);
}
}
StackDestroy(&st);
}
时间复杂度:O(N*logN)
空间复杂度:O(N)
四.归并类
1.归并排序
步骤:
而第一步的子区间的排序又可以采用同样的方法,可以使用递归或者迭代实现
递归
// 归并排序递归实现
void _MergeSort(int* a, int left, int right, int* tmp)
{
if (left >= right)
{
return;
}
_MergeSort(a, left, left+(right-left) / 2, tmp);
_MergeSort(a, left + (right - left) / 2+1,right, tmp);
int p1 = left, p2 = left + (right - left) / 2 + 1;
int i = left;
while (p1 <= left + (right - left) / 2 && p2 <= right)
{
if (a[p1] < a[p2])
{
tmp[i++] = a[p1++];
}
else
{
tmp[i++] = a[p2++];
}
}
while (p1 <= left + (right - left) / 2)
{
tmp[i++] = a[p1++];
}
while (p2 <= right)
{
tmp[i++] = a[p2++];
}
memcpy(a + left, tmp + left, 4*(right-left+1));
}
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
tmp = NULL;
}
迭代
// 归并排序非递归实现
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
for (int i = 1; i < n; i *= 2)
{
for (int left1 = 0; left1 < n; left1 += 2 * i)
{
//left1 ,left+i-1 left+i,left+2*i-1
int end1 = left1 + i - 1;
int end2 = left1 + 2 * i - 1;
int p1 = left1, p2 = left1 + i;
int j = left1;
if (end1>=n)
{
end1 = n - 1;
end2 = n-1;
}
if (left1 + i >= n)
{
end2 = p2 - 1;
}
if (end2 >= n)
{
end2 = n-1;
}
while (p1 <= end1 && p2 <= end2)
{
if (a[p1] < a[p2])
{
tmp[j++] = a[p1++];
}
else
{
tmp[j++] = a[p2++];
}
}
while (p1 <= end1)
{
tmp[j++] = a[p1++];
}
while (p2 <= end2)
{
tmp[j++] = a[p2++];
}
memcpy(a + left1, tmp + left1, 4 * (end2-left1+1));
}
}
free(tmp);
tmp = NULL;
}
时间复杂度:O(N*logN)
空间O(N)