今天分享几个排序算法:
排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次 序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排 序算法是稳定的;否则称为不稳定的。
内部排序:数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序
1.冒泡排序
冒泡排序就是逐个比较相邻两元素,经过一轮比较把最小的元素放到最前面,相当于泡沫一样向水面上浮,或者把最大的数沉底。
冒泡排序代码实现:(我实现的是升序排序)
int bubblesort(int array[],int size){
for (int i = 0; i < size; i++){
int issorted = 1; //设置一个标志位,先假设该数组是有序的
for (int j = 0; j < size - i - 1; j++){
if (array[j + 1] < array[j]){ //比较相邻两元素,把最大的放后面。
swap(array, j, j + 1);
issorted = 0; //如果有变动就将issorted的值置为0,证明不是有序的。
}
}
if (issorted == 1){ //经过一轮比较后判断issorted的值是否为1,如果为1就证明该数组已经有序。
break;
}
}
}
冒泡排序图解:
2.直接插入排序
插入排序就是在一个有序序列里插入其他数字,最贴近我们生活的例子就是打扑克牌,最开始你的牌是无序的,但经过你的调整前面的牌就会变得有序,然后再将无序序列里的牌通过和有序序列里的牌进行比较,然后得出插入位置!
直接插入排序代码实现部分:(我实现的是降序排序)
void InsertSort(int array[], int size){ //降序排序,前面是有序序列
for (int i = 0; i < size; i++){
int j;
for (j = i - 1; j >= 0 && array[j] < array[i]; j--){}//从j的序列逆着比较,遇到j中比i小的就往前走直到,直到找到合适的插入位置。
if (j + 1 != i){
int v = array[i]; //先记录下i处的值,否则移动后会被覆盖
for (int k = i ; k>j+1; k--){
array[k] = array[k - 1];
}
array[j+1] = v;
}
}
}
插入排序图解:
3.插入排序动态版
动态的意思就是边比较边移动数据,这个方法相比上面那个方法,少了些数据移动的过程。
下面是代码实现部分:(我实现的是升序排序)
void InsertSort2(int array[], int size){ //升序动态插入排序,边比边移动
for (int i = 0; i < size; i++){
int v = array[i]; //先定义一个变量v保存带插入i的值
int j;
for (j = i - 1; j>=0&&array[j]>v; j--){ //从有序序列j的末尾开始比较,只要比v大就往后移一位,直到不满足出循环后,然后就把那个要插入给v空出来了。
array[j+1] = array[j];
}
array[j+1] = v;
}
}
4.选择排序
选择排序就是每次假定第一个数为最大值,然后在无序序列中经过一轮和预定义的最大数比较后找出最大的数和无序序列的最后一个数交换。然后无序序列的长度每次减一。
选择排序代码实现如下:(我实现的是升序排序)
void SelectSort(int array[], int size){ //后面是有序序列
for (int i = 0; i < size; i++){
int max = 0; //每次假定无序序列的第一个数最大
//无序[0,size-1-i]
//有序[size-i,size-1]
for (int j = 0; j <= size - 1 - i; j++){ //再从无序的序列中找最大的和预定义的最大比较看是否交换
if (array[j] >= array[max]){
max = j;
}
}
Swap(array,max,size-i-1);
}
}
选择排序图解:
5.快速排序
快速排序主要分为两部分一部分是确定基准值,还有部分就是再对基准值两边的区间递归使用该函数,这两个过程很重要!原来我写不出快排就是因为根本就没有记清楚快排都干了什么。
快速排序实现代码如下:(我实现的是升序排序)
//求基准值下标并进行一次排序的三种方法
//基本分治法
int partition_1(int array[], int left, int right){
int begin = left;
int end = right;
int pivot = array[right]; //定义一个变量pivot保存基准值
while (begin < end){
while (begin < end&&array[begin] <= pivot){
begin++;
}
while (begin < end&&array[end] >= pivot){
end--;
}
swap(array, begin, end); //当同时遇到左边比基准值大,右边比基准值小的情况就交换
}
swap(array, begin, right); //当begin和end相遇时说明已经走完了,则将array[right]的值和begin所在的位置的值交换
return begin;
}
//双下标法
int partition_2(int array[], int left, int right){
int d = left; //d会停在第一个大于array[right]的数上
for (int i = left; i < right; i++){ //向后找比array[right]小的就和d所在位置的数交换,
if (array[i] < array[right]){ //直到i走完为止
swap(array, i, d);
d++;
}
}
swap(array,d,right); //交换d和right的值
return d;
}
//挖坑填坑法 该方法比基本分治法要快一些,因为它少了交换的次数
int partition_3(int array[], int left, int right){
int begin = left;
int end = right;
int pivot = array[right]; //先找个变量保存array[right],此时把end所在的位置看作是个坑
while (begin < end){
while (begin < end&&array[begin] <= pivot){
begin++;
}
array[end] = array[begin]; //只要遇到begin的值比pivot的值大就用begin的值来填end所在的坑,然后这样begin的位置就成了一个坑
while (begin < end&&array[end] >= pivot){
end--;
}
array[begin] = array[end]; //当遇到end的值比pivot小的时候再用end的值去填刚才begin那个位置的坑,这样end所在的位置就变成了一个坑,然后再循环上面操作。
}
array[begin] = pivot; //当begin和end相遇后证明已经走完,将pivot的放到begin所在的位置,这样就做到了pivot左边的都比它小,pivot右边的都比它大
return begin;
}
void Qsort(int array[], int left, int right){
if (left == right){ //如果左边界和右边界相等证明只有一个数了,那就是有序的
return;
}
if (left > right){ //如果左边界大于右边界证明区间里没有数,已经结束了
return;
}
int d = partition_3(array, left, right); //获取基准值下标
Qsort(array, left, d - 1); //对基准值左边的区间递归调用该函数
Qsort(array, d + 1, right); //对基准值右边的区间递归调用该函数
}
void quicksort(int array[], int size){
Qsort(array, 0, size - 1);
}
快速排序图解:
6.归并排序
归并排序就是每次求出中间位置将一个区间划分为左右两个小区间,再对两个小区间分别递归进行归并排序。归并排序和快排很类似,但也有着实质性的区别原理不同,快排是确定一个基准值,保证一次排序后基准值左边的数都小于基准值,基准值右边的数都大于基准值;而归并排序是不断合并两个有序区间达到最终的排序目的。
归并排序代码实现部分:(我实现的是升序排序)
void merge(int array[], int low, int mid, int high, int extra[]){ //合并两个有序区间,类比两个链表的合并
int i = low;
int j = mid;
int k = low;
while (i < mid&&j < high){
if (array[i] <= array[j]){ //保证稳定性
extra[k++] = array[i++];
}
else{
extra[k++] = array[j++];
}
}
while (i < mid){ //前面的区间还剩的有
extra[k++] = array[i++];
}
while (j < high){ //后面的区间还剩的有
extra[k++] = array[j++];
}
for (int x = low; x < high; x++){ //把合并后的有序区间再重新赋值给原区间
array[x] = extra[x];
}
}
void mergrinner(int array[],int low,int high,int extra[] ){ //左闭右开
if (low >= high){ //左边界>=右边界的话,说明区间就没有数,因为我是采用左闭右开的方式定义区间的。
return;
}
if (low +1 == high){ //左边界+1=右边界的话说明区间只有一个数,也就说明是有序的
return;
}
int mid = low + (high - low) / 2; //找区间的中间位置
mergrinner(array, low, mid, extra); //对mid左边的区间递归调用该函数
mergrinner(array, mid, high, extra); //对mid右边的区间递归调用该函数
merge(array, low, mid, high, extra); //当左右区间有序时则会调用该函数来合并两个有序区间
}
void mergesort(int array[],int size){
int *extra = (int*)malloc(sizeof(int)*size); //在主调函数内申请空间
mergrinner(array, 0, size, extra);
free(extra); //释放所调用的空间
}
归并排序图解:
7.堆排序
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为 小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
拿我所写的代码解释堆排序就是每次通过向下调整的方式将最大的数置于堆顶,然后将堆顶元素和数组中的最后一个元素交换,这样每经过一轮就将最大的数放到数组无序数组末尾,每找到一个最大数后无序数组长度减一!
牢记:升序排序建大堆,降序排序建小堆!
堆排序代码实现如下:(我实现的是升序排序)
void Heapify(int array[], int size, int index){ //向下调整
int leftindex = index * 2 + 1;
while ((2 * index + 1) < size){ //如果是叶子结点就不需要进入循环
int rightindex = index * 2 + 2;
int max = leftindex;
if (rightindex<size&&array[rightindex]>array[max]){
max = rightindex;
}
if (array[max] <= array[index]){
return;
}
int tmp = array[max];
array[max] = array[index];
array[index] = tmp;
index = max;
}
}
void CreateHeap(int array[],int size){ //因为写的是升序排序所以建大堆,如果是一个堆的话那么它下面一定满足是堆
for (int i = size/2 - 1; i >= 0; i--){ //因此向上调整
Heapify(array,size,i); //因为向下调整函数参数传的是最后一个非叶子结点的下标所以进行的是向上调整
}
}
void HeapSort(int array[], int size){ //升序建大堆
CreateHeap(array, size); //创建大堆
for (int i = 0; i < size; i++){
Swap(array,0,size-1-i); //将堆顶(最大元素)和无序序列最后一个元素交换
Heapify(array, size-1-i,0); //然后堆顶一变就需要向下调整,并且无序序列长度-1
}
堆排序图解:
8.各类排序的比较和分析
排序方法 | 平均情况 | 最好情况 | 最坏情况 | 辅助空间 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
直接插入排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 稳定 |
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
快速排序 | O(nlogn) | O(nlogn) | O(n^2) | O(logn)~O(n) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
最后给大家提供一个网址,里面是关于排序的视频讲解,不过是通过舞蹈的形式体现出来的,希望能够帮助大家理解:https://www.bilibili.com/video/av17449274?from=search&seid=3210928110498774502