Sort.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int DataType;
typedef int (*Pcompare)(DataType left, DataType right);
typedef struct Heap
{
DataType *arr;
int size;
int capacity;
Pcompare compare;
}Heap;
int Less(DataType left, DataType right);
int Greater(DataType left, DataType right);
void HeapSort(Heap *hp);
void CreatHeap(Heap *hp, int size);
void DownAdjust(Heap *hp, int i, int size);
void InitHeap(Heap *p, int size, int *array,Pcompare compare);
void DestroyHeap(Heap *hp);
void InsertSort(int *array, int size);
void ShellSort(int *array, int size);
void SelectSort(int *array, int size);
void QuickSortRecursion(int *array, int left, int right);
void QuickSort(int *array, int left, int right);
void MergerSort(int *array, int left, int right, int *tmp);
1.插入排序:
基本思想:每一步将一个代排的元素,按其排序码的大小,插入到前面已经排好序的一组元素的合适位置上去,直到元素全部插完为止。
当插入第i个元素时,前面的i个已经是有序的了,然后我们将第i个元素与前面的元素比较,如果前面的元素大于要插入的元素的话,将前面的元素后移,直到你要插入的元素找到合适的插入位置时插入。如下图所示:
后面的元素和第二个元素的插入一样
代码:
//普通的插入排序
void Swap(int *p, int *q)
{
int tmp = *p;
*p = *q;
*q = tmp;
}
void InsertSort(int *array, int size)
{
int i = 1;//用来标记要插入的数刚开始的位置
int end = 0;
int key = 0;//用来表示你要插入的数
assert(array);
while( i < size)
{
end = i-1;//表示插入元素前面的元素
key = array[i];//key保存你要插入的数
while(end >= 0 && key < array[end])//找到你要插入数的位置(从最后一位数开始比较,如果小于的话,将最后一位数向后移动,直到找到你要插入的位置)
{
array[end+1] = array[end];
end--;
}
array[end+1] = key;//将你要插入的数插入对应的位置
i++;
}
}
插入排序适合于元素接近有序,这样插入排序的效率比较高
插入排序最优的情况:时间效率为O(n)
插入排序最差的情况:时间效率为O(n^2)
插入排序的空间复杂度为:O(1);
直接插入排序是一种稳定的排序算法。
2.希尔排序
希尔排序是插入排序的优化:希尔排序与插入排序很相似,希尔排序不是一个接一个的排,比如第一个插入后与它后面的第i+grap(间隔个数)个元素比较。如下图所示:
//优化插入排序
//希尔排序(和插入排序差不多,只是希尔排序不是与它前一个比较,而是与它的下标相隔garp的数比较,直到间隔数为0时,停止)
void ShellSort(int *array, int size)
{
int i = 0;//用来标记要插入的数刚开始的位置
int prev = 0;
int key = 0;//用来表示你要插入的数
int garp = 3;//代表每隔几个数来比较
assert(array);
while(garp > 0)
{
i = garp;
while( i < size)
{
prev = i-garp;//用来表示插入的元素前面的元素
key = array[i];//key保存你要插入的数
while(prev >= 0 && key < array[prev])//找到你要插入数的位置(从最后一位数开始比较,如果小于的话,将最后一位数向后移动,直到找到你要插入的位置)
{
array[prev+garp] = array[prev];
prev-=garp;
}
if(prev+garp != i)//如果你要插入的位置就是你现在的位置的话,不需要改变
array[prev+garp] = key;//将你要插入的数插入对应的位置
i++;
}
garp--;
}
}
3.选择排序
基本思想:第一次遍历选出最大值和最小值,然后将最大值和最小值放到首和尾。r然后将首和尾的标记向前和向后移一步。
循环上述操作,直到最后首标记大于等于尾标记时退出。
直接选择排序的时间复杂度为O(n^2);它是一种不稳定的排序算法
代码为:
//选择排序(每次选出一个最大值和最小值)
void SelectSort(int *array, int size)
{
int beagin = 0;//用来记录每次最小值所放的位置
int end = size-1;//用来记录每次最大值所放的位置
while(beagin < end)//当最小值的位置大于最大值的位置时,停止
{
int minPos = beagin;
int maxPos = beagin;
int i = beagin;
while(i <= end)//遍历一遍,记录这次最大值和最小值所在的下标
{
if(array[minPos] > array[i])//如果
{
minPos = i;
}
if(array[maxPos] < array[i])
{
maxPos = i;
}
i++;
}
if(maxPos != end)//如果最大值在它应该在的位置的话,不用交换,如果不在的话,交换
{
Swap(&array[maxPos], &array[end]);
}
if(minPos == end)//如果最大值应该放置的位置正好时最小值标记的位置的话,记录,交换后最小值的位置
{
minPos = maxPos;
}
if(minPos != beagin)如果最小值在它应该在的位置的话,不用交换,如果不在的话,交换
{
Swap(&array[minPos], &array[beagin]);
}
beagin++;//每次将最大值最小值放在首和末时,将首尾向中间靠拢,继续找他们中间的数据的最大值和最小值
end--;
}
}
4. 堆排序:
创建堆:升序---》大堆, 降序----》小堆
假设要升序的话,首先将这个堆调整为大堆。
1.然后将堆顶的元素与堆的最后一个元素交换
2.堆的元素个数-1
3.因为改变了堆顶所以此时的堆可能不满足大堆的特性,所以重新调整堆
重复上述的操作,直到堆内的元素的个数为1时停止。
代码为:
//堆排序
//判断元素大小的函数(大堆的话,比较大小时用第二个函数,反之用第一个函数)
int Less(DataType left, DataType right)
{
return (left) < (right);
}
int Greater(DataType left, DataType right)
{
return (left) > (right);
}
//初始化堆
void InitHeap(Heap *p, int size, int *array,Pcompare compare)
{
int i = 0;
assert(p);
(p)->arr = (DataType *)malloc(sizeof(DataType)*size);
if(NULL == (p)->arr)
exit(0);
for(i = 0; i < size; i++)//初始化堆
{
(p)->arr[i] = array[i];
}
(p)->capacity = size;
(p)->size = size;
(p)->compare = compare;
}
//向下调整
void DownAdjust(Heap *hp, int i, int size)
{
int child = (i<<1) + 1;//左孩子
int parent = i;
assert(hp);
while(child < size)
{
//判断左右孩子,选出最大值
if(child + 1 < size && (hp)->compare((hp)->arr[child+1] , (hp)->arr[child]))
child++;
//左右孩子的最大值与双亲节点比较
if((hp)->compare((hp)->arr[child], (hp)->arr[parent]))
{
Swap(&(hp)->arr[child], &(hp)->arr[parent]);
}
else
{
return;
}
parent = child;
child = (parent<<1)+1;
}
}
//创建堆
void CreatHeap(Heap *hp, int size)
{
int i = size/2-1;
for(i; i >= 0; i--)
{
DownAdjust(hp, i, size);
}
}
//堆排序
void HeapSort(Heap *hp)
{
int i = 0;
assert(hp);
i = (hp)->size-1;
while(i > 0)
{
//交换堆顶和堆尾的值。将堆顶的最大值(最小值)放到现在堆的末尾
Swap(&(hp)->arr[i], &(hp)->arr[0]);
i--;//将堆的元素个数-1.(因为此时堆中最大值(最小值)已经放到它应该在的位置)
DownAdjust(hp, 0, i);//因为改变了堆顶的元素,所以现在的堆可能不满足大堆(小堆)的特性,所以需要重新调整堆
}
}
//释放
void DestroyHeap(Heap *hp)
{
assert(hp);
if(hp->arr != NULL)
free(hp->arr);
hp->arr = NULL;
hp->capacity = 0;
hp->size = 0;
}
5.快速排序:
首先在元素序列中随机选取一个元素作为基准值,然后遍历序列,大于基准值的放到基准值的左边,反之放到基准值的右边,然后重复上述的操作,直到所有元素都在相应的位置上为止。
按照基准值分割序列有三种方式:
1.hoare版本:
首先选取最右边的值为基准值,然后选取两个下标,一个指向数组的头,一个指向数组的尾,
1.两个下标分别向相反的方向遍历,左边的下标遇到大于基准值的元素时停止,右边的元素遇到小于基准值的元素停止
2. 然后交换两个下标所指的元素,
重复上面的操作。直到左边的下标大于或等于右边的下标时,退出循环,然后将左边下标所指的元素与序列的最后一个元素交换。
//三数取中法
//防止快速排序时,取到极大值或极小值
int Mid(int *array, int left, int right)
{
int mid = left+((right-left)>>1);
if(array[left] < array[right])
{
if(array[left] > array[mid])
return left;
else if(array[mid] > array[right])
return right;
else
return mid;
}
else
{
if(array[right] > array[mid])
return right;
else if(array[left] < array[mid])
return left;
else
return mid;
}
}
//快速排序中的一次分割(取一个中点,小于它的放左边,大于它的放在右边)
//首先取最后一个元素为切割的中点,然后取两个下标,一个下标begin指向数组的首,一个下标end指向数组的尾,然后开始判断,
//如果begin小于end,且以begin为下标的值小于等于key的话,begin向后移动
//如果end大于begin,且以end为下标的值大于key的话,end向前移动
//然后交换两个下标所指向的两个值
//直到begin大于等于end时退出,然后再交换begin和最后一个元素的值,这样大于key的数在key的右边,小于key的数在左边
int FastSort(int *array, int left, int right)
{
int begin = left;
int end ;
int key;
int index = right-1;
if(right-1 != Mid(array,left, right-1))//如果三数取中法的基准值是最后一个元素时,不需要改变基准值的下标
index = Mid(array,left, right-1);//记录基准值的下标
key = array[index];
//将基准值和数组的最后一个元素交换(因为我下面的排序都是取数组的最后一个元素作为基准值的)
Swap(&array[index], &array[right-1]);
end = right-1;
while(begin < end)
{
//如果begin小于end,且以begin为下标的值小于等于key的话,begin向后移动
while(begin < end && array[begin] <= key)//这里是小于等于,因为当数组中的元素全部相等时,程序会死循环
begin++;
//如果end大于begin,且以end为下标的值大于key的话,end向前移动
while(end > begin && array[end] >= key)
end--;
if(begin < end)
Swap(&array[begin], &array[end]);
}
Swap(&array[begin], &array[right-1]);
return begin;
}
2.挖坑法
挖坑法与第一种方法有点相似,挖坑法是先将两个下标分别指向数组的首(p)和尾(q),
1.然后p开始一直向后查找,遇到大于基准值的数据停止,然后将p指向的值赋给q指向的位置。
2.q一直向前查找,如果遇到小于基准值的数据时停止,然后将q指向的值赋给p所指向的位置,
循环上面的操作,直到指向p >= q时退出,然后将基准值放在首下标所指向的位置。
这样的话,一次分割结束。
//挖坑法(快速排序中的切割方法中的一种)
//首先选择数组的最后一个元素为中点,然后设置两个下标,一个指向数组的首,一个指向数组的尾
//然后开始比较,如果begin指向的数比key小的话,继续向后寻找,如果大于key的话,停止,将现在指向的值赋给end指向的值
//如果end指向的数据大于key时向前走,如果小于时停止,将现在指向的值赋给begin指向的位置
//直到begin大于等于end时退出,
//此时,将key赋给begin指向的位置
int ExcavateSort(int *array, int left, int right)
{
int begin = left;
int end ;
int key;
int index= right-1;
if(right-1 != Mid(array,left, right-1))//判断如果基准值是最后一个元素时,不需要再改变基准值的下标
index = Mid(array,left, right-1);//记录基准值的下标
key = array[index];
//将基准值和数组的最后一个元素交换(因为我下面的排序都是取数组的最后一个元素作为基准值的)
Swap(&array[index], &array[right-1]);
end = right-1;
while(begin < end)
{
//当begin指向的元素小于key且begin小于end时,向前走
while(begin < end && array[begin] <= key)
{
begin++;
}
//将此时begin指向的值赋给end位置
if(begin < end)
{
array[end] = array[begin];
end--;
}
while(end > begin && array[end] >= key)
{
end--;
}
if(end > begin)
{
array[begin] = array[end];
begin++;
}
}
array[begin] = key;
return begin;
}
3.前后指针
首先建立两个下标,一个指向数组的首cur,一个指向cur后面的下标prve(主要思想就是:prev指向第一个大于基准值的前面的元素,如果后面有小于基准值的数据时,将该数据与第一个大于基准值的数据交换,然后prev+1,再次指向第一个大于基准值数据的前一个数据)
cur向后一次向后遍历:
如果遇到cur指向的数据小于基准值并且cur != prev+1的话,将cur指向的值和prev指向的值交换。
如果cur == prev+1的话,cur和prev同时向后移动。
如果cur遇到的值大于基准值时,cur向后移动,prev指向的位置不变
循环上面的操作,直到cur指向最后一个元素时,停止。
//前后下标法(快速排序分割的第三种方法)
//首先用三数取中法找到一个分割点,避免极值为分割点,
//用其中一个下标cur指向数组的第一个元素,用另一个下标Prve指向cur-1,如果cur指向的值大于key时,cur向后移动,Prve不动,如果cur指向的数据小于key且Prve+1 < cur时,交换cur和Prve指向的数据
//重复上述操作,直到cur找到最后一个数据时退出
//最后将最后一个元素与FirstGreterKey指向的值交换。
int FrontAndBack(int *array, int left, int right)
{
int cur = left;
int index = right-1;
int key;
int Prve;
//如果三数取中法取得数据就是最后一个元素的话,不需要交换
if(right-1 != Mid(array,left, right-1))
index = Mid(array,left, right-1);
key = array[index];
//将基准值和数组的最后一个元素交换(因为我下面的排序都是取数组的最后一个元素作为基准值的)
Swap(&array[index], &array[right-1]);
Prve =cur-1;//用来记录大于key的前一个值
while(cur < right)
{
if(array[cur] < key && ++Prve < cur)//如果cur指向的数据小于key且Prve+1 < cur时,交换cur和Prve指向的数据
{
Swap(&array[cur], &array[Prve]);
}
cur++;
}
//将最后一个元素放到它对应的位置上
if(++Prve < right)//判断Prve是否越界
Swap(&array[Prve], &array[right-1]);
return Prve;
}
递归快速排序函数
//递归的快速排序
void QuickSortRecursion(int *array, int left, int right)
{
//如果left等于right时,只剩下一个元素,所以就不用再排序
if(left < right)
{
int MidPoint = FastSort(array,left,right);
QuickSortRecursion(array, left, MidPoint);
QuickSortRecursion(array, MidPoint+1,right);
}
}
非递归快速排序
//非递归的快速排序
//用栈来实现快排的非递归
//二叉树的前序遍历一样,先排序它左区间的左区间,直到它左区间只有一个元素时停止,然后再排它右区间,直到只有一个元素为止
//然后向栈中取出左右区间,利用这左右区间进行一次排序,
//一次排序后先将基准值右边的左右边界入栈,然后将基准值左边区间的左右边界入栈,因为每次出栈都会取栈顶的元素,所以下次渠道的还是左区间
//重复上两步的操作,直到栈为空时,退出
void QuickSort(int *array, int left, int right)
{
Stack q = {0};
int begin = left;
int end = right;
StackInit(&q, right);
//首先将数组的左右边界入栈
StackPush(&q, end);
StackPush(&q, begin);
while(q.top != 0)//栈空时退出
{
int mid = 0;
begin = StackTopData(&q);
StackPop(&q);
end = StackTopData(&q);
StackPop(&q);
//将栈顶取到的区间之间的元素进行一次排序
mid = FrontAndBack(array, begin, end);
//如果它的右区间有不止一个元素,将它的右区间入栈
if(end > mid+1)
{
StackPush(&q, end);
StackPush(&q, mid+1);
}
//如果左区间有不只一个元素时,将它的左区间入栈
if(begin < mid)
{
StackPush(&q, mid);
StackPush(&q, begin);
}
}
}
快排的最坏的情况:序列接近有序
快排的最优的情况:每次基准值都是再区间的中间
快排的优化:拿数组的第一个元素、最中间的元素、末尾的元素,三个元素排序,取中间的值
使用场景:数据随机,数据量大
归并的递归排序
基本思想:将代排序的元素序列分成两个长度相等的子序列,对每一个子序列排序,然后将他们合并成一个序列。合并两个子序列的过程称为二路归并。
和二叉树的后续遍历相同首先将数组分为每个元素自己为一组的情况,然后两两合并,
//归并两个区间的数据
void MergerData(int *array, int left, int mid, int right, int *tmp)
{
int begin1 = left;
int i = left;
int end1 = mid;
int begin2 = mid+1;
int end2 = right;
int index = begin1;
//将两个区间里的数据从头开始比较,哪边比较小就往tmp里插入,直到其中的一个区间结束
while(begin1 <= end1 && begin2 <= end2)
{
if(array[begin1] <= array[begin2])//如果前面的区间的begin1指向的数据小与后面区间begin2指向的数据时,将begin1指向的数据插入tmp中
{
tmp[index] = array[begin1];
begin1++;
index++;
}
else
{
tmp[index] = array[begin2];
begin2++;
index++;
}
}
//查看哪个区间还有剩余的元素,将剩余的元素之间插入到tmp中
while(begin1 <= end1)
{
tmp[index] = array[begin1];
begin1++;
index++;
}
while(begin2 <= end2)
{
tmp[index] = array[begin2];
begin2++;
index++;
}
//每次都更新原来的数组
while(i <= right)
{
array[i] = tmp[i];
i++;
}
}
//归并排序
void MergerSort(int *array, int left, int right, int *tmp)
{
//与二叉树的后续递归排序特性相同
if(left < right)
{
int mid = left + ((right-left)>>1);
MergerSort(array, left, mid, tmp);
MergerSort(array, mid+1, right, tmp);
MergerData(array, left, mid, right, tmp);
}
}
归并的非递归排序:
首先我们把数组的元素看作一个元素是一个组,然后将他们两两归并
//归并的非递归实现
void MergerSortNor(int *array, int left, int right, int *tmp)
{
int size = 1;
int i = left;
while(size <= right)
{
for(i = 0; i < right; i+=2*size)
{
int mid = i+size-1;
int begin = i;
int end = i+ 2*size;
//如果越界
if(mid > right)
{
mid = i;
}
if(end > right)
{
end = right;
}
MergerData(array, begin, mid, end, tmp);
}
size*=2;
}
}
//归并两个区间的元素
void MergerData(int *array, int left, int mid, int right, int *tmp)
{
int begin1 = left;
int i = left;
int end1 = mid;
int begin2 = mid+1;
int end2 = right;
int index = begin1;
//将两个区间里的数据从头开始比较,哪边比较小就往tmp里插入,直到其中的一个区间结束
while(begin1 <= end1 && begin2 <= end2)
{
if(array[begin1] <= array[begin2])//如果前面的区间的begin1指向的数据小与后面区间begin2指向的数据时,将begin1指向的数据插入tmp中
{
tmp[index] = array[begin1];
begin1++;
index++;
}
else
{
tmp[index] = array[begin2];
begin2++;
index++;
}
}
//查看哪个区间还有剩余的元素,将剩余的元素之间插入到tmp中
while(begin1 <= end1)
{
tmp[index] = array[begin1];
begin1++;
index++;
}
while(begin2 <= end2)
{
tmp[index] = array[begin2];
begin2++;
index++;
}
//将数组内归并的部分更新
while(i <= right)
{
array[i] = tmp[i];
i++;
}
}
main.c
#include"Sort.h"
void InsertText()
{
int array[10] = {4, 2, 5, 1, 9, 7 ,8, 0, 3, 6};
//int array[9] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
InsertSort(array, sizeof(array)/sizeof(array[0]));
ShellSort(array, sizeof(array)/sizeof(array[0]));
SelectSort(array, 9);
QuickSortRecursion(array, 0, sizeof(array)/sizeof(array[0]));
QuickSort(array, 0, sizeof(array)/sizeof(array[0]));
}
void HeapText()
{
int array[10] = {4, 2, 5, 1, 9, 7 ,8, 0, 3, 6};
Heap hp = {0};
InitHeap(&hp, 10, array,Greater);
CreatHeap(&hp, 10);
HeapSort(&hp);
DestroyHeap(&hp);
}
void MergerText()
{
int array[10] = {4, 2, 5, 1, 9, 7 ,8, 0, 3, 6};
int i = 0;
int *tmp = (int *)malloc(sizeof(int)*sizeof(array)/sizeof(array[0]));
if(tmp == NULL)
{
exit(0);
}
MergerSortNor(array, 0, sizeof(array)/sizeof(array[0])-1, tmp);
free(tmp);
tmp = NULL;
}
int main()
{
InsertText();
HeapText();
MergerText();
return 0;
}
各排序算法的比较
类别 | 排序方法 | 时间复杂度 | 空间复杂度 | 稳定性 | ||
平均情况 | 最好情况 | 最坏情况 | ||||
插入排序 | 插入排序 | O(N^2) | O(N) | O(N^2) | O(1) | 稳定 |
希尔排序 | O(N^1.3) | O(N) | O(N^2) | O(1) | 不稳定 | |
选择排序 | 选择排序 | O(N^2) | O(N^2) | O(N^2) | O(1) | 不稳定 |
堆排序 | O(N*lgN) | O(N*lgN) | O(N*lgN) | O(1) | 不稳定 | |
交换排序 | 冒泡排序 | O(N^2) | O(N) | O(N^2) | O(1) | 稳定 |
快速排序 | O(N*lgN) | O(N*lgN) | O(N^2) | O(lgN) | 不稳定 | |
归并排序 | 归并排序 | O(N*lgN) | O(N*lgN) | O(N*lgN) | O(N) | 稳定 |