详解—数据结构—<常用排序>基本实现和代码分析

目录

一.排序的概念及其运用

1.1排序的概念

1.2排序运用​编辑

1.3 常见的排序算法​编辑

二.常见排序算法的实现

2.1 插入排序

2.1.1基本思想:

2.1.2直接插入排序:

2.1.3 希尔排序( 缩小增量排序 )

2.2 选择排序

2.2.1基本思想:

2.2.2 直接选择排序:

2.2.3 堆排序

2.3 交换排序

2.3.1冒泡排序​编辑

2.3.2 快速排序

2.3.2.1. hoare版本

2.3.2.2. 挖坑法

2.3.2.3. 前后指针版本

2.3.2.4. 非递归版本

2.3.3 快速排序优化

2.3.3.1. 三数取中法选key

2.3.3.2. 递归到小的子区间时,可以考虑使用插入排序

2.4 归并排序

2.5 非比较排序

三.排序算法复杂度及稳定性分析​编辑


一.排序的概念及其运用


1.1排序的概念

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
内部排序:数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

1.2排序运用

1.3 常见的排序算法

912. 排序数组 - 力扣(LeetCode)力扣的排序OJ 题可以用各种排序跑这个OJ

二.常见排序算法的实现

2.1 插入排序

2.1.1基本思想:

直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。

实际中我们玩扑克牌时,就用了插入排序的思想

2.1.2直接插入排序:

当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2],…的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移

void InsertSort(int a[], int n)
{//i 每一个数都算是插入进去的for (int i = 0;i < n - 1;i++){//end 数组内数据个数int end = i; //tmp 为插入数据int tmp = a[end + 1];//插入数据与数组内数据比较,如果插入数据比数组最后一个数据大 好 直接原地插入 //跳出循环 ,否则,跟数组内数据比较 找到数组内合适的位置插入while (end>=0) // 数组内数据遍历到a【0】{if (tmp < a[end]) //插入数据 与 数组最后位置比较{a[end + 1] = a[end];  // 小于 往前走 --end;}else{  break;    //大于 结束}a[end + 1] = tmp; //}}
}

直接插入排序的特性总结:

1. 元素集合越接近有序,直接插入排序算法的时间效率越高
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1),它是一种稳定的排序算法
4. 稳定性:稳定

2.1.3 希尔排序( 缩小增量排序 )

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序

void ShellSort(int* a, int n)
{//希尔排序//1. 先预排序// 预排序 就是  把要排序数组分成 gap组 然后开始比较 交换// //2.在插入排序// gap > 1 预排序// gap == 1 直接插入排序int gap = n;while (gap > 1) //{// gap = gap / 2; gap = gap / 3 + 1;// 如果不加1  就有可能到不了 1 //当 gap  =1 时就是直插  ,gap 越小 与排序越接近排序好的状态for (int i = 0; i < n - gap; ++i){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;}}
}

希尔排序的特性总结:

1. 希尔排序是对直接插入排序的优化。
2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
3. 希尔排序的时间复杂度不好计算,需要进行推导,推导出来平均时间复杂度: O(N^1.3—N^2)
4. 稳定性:不稳定

2.2 选择排序

2.2.1基本思想:

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

2.2.2 直接选择排序:

在元素集合array[i]--array[n-1]中选择关键码最大(小)的数据元素


若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换


在剩余的array[i]--array[n-2](array[i+1]--array[n-1])集合中,重复上述步骤,直到集合剩余1个元素

void SelectSort(int* a, int n)
{int begin = 0, end = n - 1;while (begin < end){int mini = begin, maxi = begin;for (int i = begin + 1; i <= end; ++i){if (a[i] < a[mini])//遍历数组 找最小数据{mini = i;}if (a[i] > a[maxi])//找最大数据{maxi = i;}}Swap(&a[begin], &a[mini]); //交换最开始和 最小的数据if (maxi == begin)    //如果最大的是最开始的数据,上面交换最大的数据被换到了最小的数据位置上maxi = mini;      //把最小数据的位置交给最大数据Swap(&a[end], &a[maxi]);//交换最后和 最大的数据++begin;--end;}
}

直接选择排序的特性总结:

1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1)
4. 稳定性:不稳定

2.2.3 堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆

//向下调整建堆
void AdjustDown(int* a, int n, int parent)
{int child = parent * 2 + 1;while (child < n){// 确认child指向大的那个孩子if (child + 1 < n && a[child + 1] > a[child]){++child;}// 1、孩子大于父亲,交换,继续向下调整// 2、孩子小于父亲,则调整结束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)// 升序:建大堆for (int i = (n - 1 - 1) / 2; i >= 0; --i){AdjustDown(a, n, i);}// O(N*logN)int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);--end;}
}

堆排序的特性总结:

1. 堆排序使用堆来选数,效率就高了很多。
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(1)
4. 稳定性:不稳定

2.3 交换排序

基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

2.3.1冒泡排序

void BubbleSort(int* a, int n)
{int i = 0;for (i = 0;i < n - 1;i++){int j = 0;for (j = 0;j < n - i - 1;j++){if (a[j] > a[j + 1]){int tmp = a[j];a[j] = a[j + 1];a[j + 1] = tmp;}}}
}

冒泡排序的特性总结:

1. 冒泡排序是一种非常容易理解的排序
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1)
4. 稳定性:稳定

2.3.2 快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

将区间按照基准值划分为左右两半部分的常见方式有:

1. hoare版本
2. 挖坑法
3. 前后指针版本

4.非递归版本

2.3.2.1. hoare版本

1、选出一个key,一般是最左边或是最右边的。
2、定义一个begin和一个end,begin从左向右走,end从右向左走。(需要注意的是:若选择最左边的数据作为key,则需要end先走;若选择最右边的数据作为key,则需要bengin先走)。
3、在走的过程中,若end遇到小于key的数,则停下,begin开始走,直到begin遇到一个大于key的数时,将begin和right的内容交换,end再次开始走,如此进行下去,直到begin和end最终相遇,此时将相遇点的内容与key交换即可。(选取最左边的值作为key)
4.此时key的左边都是小于key的数,key的右边都是大于key的数
5.将key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作,此时此部分已有序

void QuickSort(int* a, int begin, int end)
{if (begin >= end){return;}int left = begin, right = end;int keyi = left;while (left < right){// 右边先走,找小while (left < right && a[right] >= a[keyi]){--right;}// 左边再走,找大while (left < right && a[left] <= a[keyi]){++left;}Swap(&a[left], &a[right]);}Swap(&a[left], &a[keyi]);keyi = left;// [begin, keyi-1]  keyi [keyi+1, end]QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}
2.3.2.2. 挖坑法

挖坑法思路与hoare版本(左右指针法)思路类似
1.选出一个数据(一般是最左边或是最右边的)存放在key变量中,在该数据位置形成一个坑


2、还是定义一个L和一个R,L从左向右走,R从右向左走。(若在最左边挖坑,则需要R先走;若在最右边挖坑,则需要L先走)

后面的思路与hoare版本思路类似在此处就不说了

//快速排序法  挖坑法
void QuickSort1(int* arr, int begin, int end)
{if (begin >= end)return;int left = begin,right = end;int key = arr[begin];while (begin < end){//找小while (arr[end] >= key && begin < end){--end;}//小的放到左边的坑里arr[begin] = arr[end];//找大while (arr[begin] <= key && begin < end){++begin;}//大的放到右边的坑里arr[end] = arr[begin];}arr[begin] = key;int keyi = begin;//[left,keyi-1]keyi[keyi+1,right]QuickSort1(arr, left, keyi - 1);QuickSort1(arr, keyi + 1, right);
}
2.3.2.3. 前后指针版本

1、选出一个key,一般是最左边或是最右边的。


2、起始时,prev指针指向序列开头,cur指针指向prev+1。


3、若cur指向的内容小于key,则prev先向后移动一位,然后交换prev和cur指针指向的内容,然后cur指针++;若cur指向的内容大于key,则cur指针直接++。如此进行下去,直到cur到达end位置,此时将key和++prev指针指向的内容交换即可。

经过一次单趟排序,最终也能使得key左边的数据全部都小于key,key右边的数据全部都大于key。

然后也还是将key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作

//快速排序法  前后指针版本
void QuickSort2(int* arr, int begin, int end)
{if (begin >= end)return;int cur = begin, prev = begin - 1;int keyi = end;while (cur != keyi){if (arr[cur] < arr[keyi] && ++prev != cur){swap(&arr[cur], &arr[prev]);}++cur;}swap(&arr[++prev],&arr[keyi]);keyi = prev;//[begin,keyi -1]keyi[keyi+1,end]QuickSort2(arr, begin, keyi - 1);QuickSort2(arr, keyi + 1, end);}
2.3.2.4. 非递归版本
void QuickSortNonR(int* a, int begin, int end)
{Stack st;StackInit(&st);StackPush(&st, begin);StackPush(&st, end);while (!StackEmpty(&st)){int right = StackTop(&st);StackPop(&st);int left = StackTop(&st);StackPop(&st);int keyi = left;int prev = left, cur = right + 1;while (cur <= end){// 找到比key小的值时,跟++prev位置交换,小的往前翻,大的往后翻if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[prev], &a[cur]);++cur;}Swap(&a[prev], &a[keyi]);keyi = prev;// [left, keyi-1] keyi [keyi+1, right]if (keyi + 1 < right){StackPush(&st, keyi + 1);StackPush(&st, right);}if (left < keyi - 1){StackPush(&st, left);StackPush(&st, keyi - 1);}}StackDestroy(&st);
}

2.3.3 快速排序优化

2.3.3.1. 三数取中法选key
int Fetchmid(int* a, int begin, int end)
{int mid = (begin + end) / 2;if (a[begin] < a[mid]){if (a[mid] < a[end]){return mid;}else if (a[begin] > a[end]){return begin;}else{return end;}}else // a[begin] > a[mid]{if (a[mid] > a[end]){return mid;}else if (a[begin] < a[end]){return begin;}else{return end;}}
}
2.3.3.2. 递归到小的子区间时,可以考虑使用插入排序
void QuickSort(int* a, int begin, int end)
{if (begin >= end){return;}//小区间优化if (end - begin < 12){//当小于12 个数的时候用直接插入法插入InsertSort(a+begin, end - begin);}else{int left = begin, right = end;int keyi = Fetchmid(a,begin, end);while (left < right){// 右边先走,找小while (left < right && a[right] >= a[keyi]){--right;}// 左边再走,找大while (left < right && a[left] <= a[keyi]){++left;}Swap(&a[left], &a[right]);}Swap(&a[left], &a[keyi]);keyi = left;// [begin, keyi-1]  keyi [keyi+1, end]QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);}
}

快速排序的特性总结:

1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
2. 时间复杂度:O(N*logN)

3. 空间复杂度:O(logN)
4. 稳定性:不稳定

2.4 归并排序

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

// 归并排序递归实现
void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc");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");exit(-1);}// 归并每组数据个数,从1开始,因为1个认为是有序的,可以直接归并int rangeN = 1;while (rangeN < n){for (int j = 0;j < n;j += 2 * rangeN){int begin1 = j; int end1 = j+ rangeN -1;int begin2 = j+ rangeN, end2 = j + 2 * rangeN - 1;if (end1 > n)//end1越界  begin2 和 end2 肯定越界{break;}else if (begin2 > n)//begin2 越界  end2 肯定越界{break;}else if (end2 > n){end2 = n - 1;}int i = j;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 + j, tmp + j, sizeof(int) * (end2 - j + 1));}rangeN *= 2;}free(tmp);tmp = NULL;
}

归并排序的特性总结:

1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(N)
4. 稳定性:稳定

2.5 非比较排序

计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤:

1. 统计相同元素出现次数
2. 根据统计的结果将序列回收到原来的序列中

void CountSort(int* arr, int size)
{int i;int minValue = arr[0];int maxValue = arr[0];int range = 0;int* tmp = 0;int count = 0;for (i = 0; i < size; i++)//计算数据的分散空间{if (arr[i] < minValue) {minValue = arr[i];}if (arr[i] > maxValue) {maxValue = arr[i];}}range = maxValue - minValue + 1;tmp = (int*)malloc(sizeof(arr[0]) * size);//申请辅助空间if (tmp == NULL)return;memset(tmp, 0, sizeof(int) * range);//初始化for (i = 0; i < size; i++)//统计每个元素出现的次数{tmp[arr[i] - minValue]++;}for (i = 0;i < range;i++)//通过统计tmp[];回收元素{while (tmp[i]--){arr[count++] = i + minValue;}}free(tmp);
}

计数排序的特性总结:

1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
2. 时间复杂度:O(MAX(N,范围))
3. 空间复杂度:O(范围)
4. 稳定性:稳定

三.排序算法复杂度及稳定性分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/308569.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[RoarCTF 2019]Easy Java(java web)

题目 页面如下 页面长得像sql注入 点击help看一下 这里需要了解java web目录结构 WEB INF:Java的web应用安全目录&#xff1b; 此外如果想在页面访问WEB-INF应用里面的文件&#xff0c;必须要通过web.xml进行相应的映射才能访问&#xff1b; WEB-INF是Java Web应用程序中的一…

传奇私服教程,新手小白速速下载!

传奇私服教程&#xff0c;新手小白速速下载&#xff01; 第二十课-封玩家账号登陆-封玩家机器码登陆.zip 第十九课-快速搭建FTP服务器教程配套工具.zip 第十八课-绿盟GOM1108引擎登陆器配置防劫持列表教... 第十七课-最新访问网站自动弹出加群教程.zip 修复沙城捐献不了或者捐献…

mac node基本操作

1 查看所有版本 npm view node versions输出 2 查看已经安装的版本 n list3 安装指定版本 sudo -E n 16.0.04 切换版本 sudo n 16.0.05 查看版本 node -v

HackTheBox - Medium - Linux - Bagel

Bagel 今天我开始了《Red Team Development and Operations A Practical Guide》的学习&#xff0c;保持学习&#xff0c;后面差不多到时机后就学CRTOⅡ Bagel 是一款中等难度的 Linux 机器&#xff0c;其特点是电子商店容易受到路径遍历攻击&#xff0c;通过该攻击可以获取应…

Vue学习day_03

普通组件的注册 局部注册: 创建一个components的文件夹 在里面写上对应的.vue文件 在对应的vue里面写上对应的3部分 template写上对应的核心代码 盒子等 style 写上对应的css修饰 在App.vue里面进行引用 import 导包 格式是 import 起个名字 from 位置 在写一个component…

2024年PMP考试新考纲-PMBOK第七版-【模型、方法和工件】真题解析

今天我们继续来看第七版PMBOK的考题&#xff0c;前面已经介绍了新考纲下最近几年价值交付系统、项目管理原则、项目绩效域、裁剪方面的部分真题和详细解析&#xff0c;今天来看PMBOK第七版的第四部分【模型、方法和工件】这个章节相关的真题。 PMBOK第七版中专门把模型、方法和…

代码随想录刷题笔记(DAY3)

今日总结&#xff1a;虽然之前刷过链表&#xff0c;但这次做的是有些费力的&#xff0c;也有了更深的理解。整理完今天的 Vue 笔记就睡。。。 DAY 3 01. 移除链表元素&#xff08;No. 203&#xff09; 题目链接&#xff1a;https://leetcode.cn/problems/remove-linked-list-…

求解拍频的信号特征

这张图上&#xff0c;时域已经明显产生调幅波的拍频特征。利用宏观的拍频特征可以肉眼识读两个信号的频差&#xff1a; 一秒是69.42个像素。拍频周期是&#xff1a;21.857像素。所以&#xff0c;拍频的周期是&#xff1a;3.7161Hz. 其中一个频率是50Hz&#xff0c;另一个频率…

[大厂实践] Chick-fil-A的服务API流程实践

本文介绍了美国快餐连锁巨头Chick-fil-A在技术团队中引入Buf和Connect解耦API依赖并实现了良好的API定义模式。原文: Connect(ing) Chick-fil-A 背景 2018年&#xff0c;Chick-fil-A的客户技术团队遇到了一些API问题。该团队擅长构建API&#xff0c;这些API可以实现一些了不起的…

如何拍摄好VR全景图片,VR全景图片后期处理有什么技巧

引言&#xff1a; VR全景图片是一种以全景视角呈现场景的图片&#xff0c;通过VR技术可以将用户带入虚拟的环境中&#xff0c;给人一种身临其境的感觉&#xff0c;那么如何才能更好的制作让人满意的全景图片呢&#xff1f; 一&#xff0e;如何拍摄好VR全景图片 1.选择合适的拍…

HarmonyOS自学-Day4(TodoList案例)

目录 文章声明⭐⭐⭐让我们开始今天的学习吧&#xff01;TodoList小案例 文章声明⭐⭐⭐ 该文章为我&#xff08;有编程语言基础&#xff0c;非编程小白&#xff09;的 HarmonyOS自学笔记&#xff0c;此类文章笔记我会默认大家都学过前端相关的知识知识来源为 HarmonyOS官方文…

傅里叶变换的性质之二维DFT的平移性和旋转性——冈萨雷斯数字图像处理

平移性原理 基本概念&#xff1a; 平移性指的是函数在空间域内平移时&#xff0c;其频率域表示也相应改变的性质。 在二维DFT中&#xff0c;当一个图像在空间域内平移时&#xff0c;其频率域表示&#xff08;即DFT&#xff09;的振幅不会改变&#xff0c;但相位会改变。 数学…