【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

2024-09-09 17:28

本文主要是介绍【数据结构】——原来排序算法搞懂这些就行,轻松拿捏,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值

基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。

快速排序实现主框架:

//快速排序 
void QuickSort(int* arr, int left, int right)
{if (left >= right){return;}//keyi即是基准值int keyi = _QuickSort1(arr, left, right);//实现找基准值的方法QuickSort(arr, left, keyi - 1);QuickSort(arr, keyi + 1, right);}

快速排序找基准值三种方法

Swap方法的实现 ,即交换两个数的值

void Swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}

hoare版本

算法思路:

1)创建左右指针,确定基准值

2)从右向左找出比基准值小的数据,从左向右找比基准值大的数据,左右指针数据交换,进入下次循环

问题1:为什么跳出循环后right位置的值⼀定不大于key? 

当 left > right 时,即right⾛到left的左侧,而left扫描过的数据均不大于key,因此right此时指向的数据⼀定不大于key

问题2:为什么left 和 right指定的数据和key值相等时也要交换?

 相等的值参与交换确实有⼀些额外消耗。实际还有各种复杂的场景,假设数组中的数据⼤量   重复时, 无法进行有效的分割排序。

int _QuickSort1(int* arr, int left, int right)
{int keyi = left;++left;while (left <= right){while (left <= right && arr[right] > arr[keyi]){right--;}while (left <= right && arr[left] < arr[keyi]){left++;}if (left <= right){Swap(&arr[left++], &arr[right--]);}}Swap(&arr[keyi], &arr[right]);return right;
}

挖坑法

思路: 创建左右指针。首先从右向左找出比基准小的数据,找到后立即放入左边坑中,当前位置变为新的"坑",然后从左向右找出比基准大的数据,找到后立即放入右边坑中,当前位置变为新的"坑",结束循环后将最开始存储的分界值放入当前的"坑"中,返回当前"坑"下标(即分界值下标)

int _QuickSort2(int* arr, int left, int right)
{int hole = left;//坑int key = arr[hole];//坑位数据while (left < right){while (left < right && arr[left] >= key){--right;}arr[hole] = arr[right];hole = right;while (left < right && arr[left] < key){++left;}arr[hole] = arr[left];hole = left;}arr[hole] = arr[left];return hole;
}

lomuto前后指针

创建前后指针,从左往右找比基准值小的进行交换,使得小的都排在基准值的左边。

int _QuickSort3(int* arr, int left, int right)
{int prev = left, cur = left + 1;int keyi = left;while (cur <= right){if (arr[cur] < arr[keyi] && ++prev != cur){Swap(&arr[cur], &arr[prev]);}++cur;}Swap(&arr[keyi], &arr[prev]);return prev;
}

以上就是快速排序当中查找基准值的三种方法 

 快速排序特性总结:

1. 时间复杂度:O(nlogn) 

2. 空间复杂度:O(logn)

快速排序非递归版本,借助数据结构栈 

void QuickSortNonR(int* arr, int left, int right)
{ST st;STInit(&st);//栈的初始化STPush(&st, right);//压栈STPush(&st, left);//压栈while (!STEmpty(&st)){//取栈顶元素两次int begin = STTop(&st);STPop(&st);int end = STTop(&st);STPop(&st);//找基准值[begin,end]int prev = begin;int cur = begin + 1;int keyi = begin;while (cur <= end){if (arr[cur] < arr[keyi] && ++prev != cur){Swap(&arr[cur], &arr[prev]);}cur++;}Swap(&arr[keyi], &arr[prev]);keyi = prev;//根据基准值划分左右区间//左区间:[begin,keyi-1]//右区间:[keyi+1,end]if (keyi + 1 < end){STPush(&st, end);STPush(&st,keyi + 1);}if (keyi - 1 > begin){STPush(&st, keyi - 1);STPush(&st, begin);}}STDestroy(&st);//销毁
}

冒泡排序 

冒泡排序核心思想是通过反复遍历待排序的序列,比较相邻的元素并交换它们的位置,使得每一趟遍历后,最大的元素逐渐"冒泡"到序列的末尾。

核心步骤如下:

  1. 比较相邻的元素:从第一个元素开始,依次比较相邻的两个元素。

    • 如果前一个元素大于后一个元素,则交换它们的位置。
  2. 继续遍历序列:一趟遍历后,最大的元素会被“冒泡”到序列的末尾。

  3. 重复遍历:从头开始再进行遍历,对剩下的元素重复比较和交换操作,直到所有元素都按顺序排列。

  4. 优化(可选):如果在某一趟遍历中没有发生任何交换,说明序列已经有序,可以提前终止排序。

void BubbleSort(int* arr, int n)
{for (int i = 0; i < n; i++){int exchange = 0;for (int j = 0; j < n - i - 1; j++){//升序if (arr[j] > arr[j + 1]){exchange = 1;Swap(&arr[j], &arr[j + 1]);}}if (exchange == 0){break;}}
}

 其时间复杂度为 O(n²),由于每次都要遍历未排序的部分,并且重复多次比较操作,因此效率较低。

直接插入排序 

直接插入排序(Direct Insertion Sort)的原理是通过逐步将待排序数组中的元素插入到已排序部分的正确位置,最终实现排序。它是一种简单且直观的排序算法,尤其适用于小规模或近乎有序的数据集。

原理步骤:

  1. 初始化已排序序列:假设数组的第一个元素已经是有序的,直接跳过。

  2. 逐个插入元素:从第二个元素开始,逐个将每个元素插入到前面已经排好序的部分中。

    • 将当前元素与前面已经排序好的元素依次比较,找到它的正确位置。
    • 如果当前元素比已排序部分的某个元素小,则将已排序部分的元素向后移动,空出位置给当前元素。
  3. 插入完成:重复这个过程,直到所有元素都被插入正确位置,排序完成。

举例说明:

假设有一个数组 [5, 2, 9, 1, 5, 6] 需要排序,步骤如下:

  • 初始数组:[5, 2, 9, 1, 5, 6]
  • 第一轮(从第二个元素开始,即 2):比较 2 和 5,2 小于 5,将 5 向后移,插入 2。得到:[2, 5, 9, 1, 5, 6]
  • 第二轮(9):9 比 5 大,直接进入下一个。得到:[2, 5, 9, 1, 5, 6]
  • 第三轮(1):1 小于 9、5 和 2,将它们都向后移,插入 1。得到:[1, 2, 5, 9, 5, 6]
  • 第四轮(5):5 小于 9,移位插入 5。得到:[1, 2, 5, 5, 9, 6]
  • 第五轮(6):6 小于 9,移位插入 6。得到:[1, 2, 5, 5, 6, 9]
void InsertSort(int* arr, int n)
{for (int i = 0; i < n - 1; i++){int end = i;int tmp = arr[end + 1];while (end >= 0){if (arr[end] > tmp){arr[end + 1] = arr[end];end--;}else{break;}}arr[end + 1] = tmp;}
}

希尔排序 

希尔排序(Shell Sort)是插入排序的改进版,它通过比较和交换不相邻的元素来减少数据的移动次数,以加速排序过程。希尔排序的核心思想是将待排序的序列按一定间隔分组,分别对每一组进行插入排序,逐步缩小间隔,直到间隔为 1 时,完成最终的插入排序。

原理步骤:

  1. 确定间隔序列:首先选择一个间隔(也称“增量”),将整个序列按照这个间隔进行分组。例如,间隔为 5 时,第 1 个元素与第 6 个元素、第二个元素与第 7 个元素形成一组,依次类推。

  2. 组内排序:对每一组进行插入排序。在每个组内的元素之间的距离由间隔确定,插入排序的操作类似于直接插入排序,但由于间隔较大,能使元素迅速向正确的位置移动,减少了总的移动次数。

  3. 缩小间隔:缩小间隔并重复步骤 2。常见的缩小方式是将间隔减半,直到间隔为 1。

  4. 完成排序:当间隔为 1 时,整个序列已经近乎有序,最后一次插入排序将序列排好。

举例说明:

假设对数组 [23, 29, 15, 19, 31, 7, 9, 5, 2] 进行希尔排序,初始数组如下:

  • 原数组:[23, 29, 15, 19, 31, 7, 9, 5, 2]

第一步:假设初始间隔为 4,将数组分组进行插入排序:

  • 对于第 1, 5, 9 号元素:[23, 31, 2] 进行排序,结果为 [2, 23, 31]。
  • 对于第 2, 6 号元素:[29, 7] 进行排序,结果为 [7, 29]。
  • 对于第 3, 7 号元素:[15, 9] 进行排序,结果为 [9, 15]。
  • 对于第 4, 8 号元素:[19, 5] 进行排序,结果为 [5, 19]。

结果数组:[2, 7, 9, 5, 23, 29, 15, 19, 31]

第二步:缩小间隔到 2,继续分组排序:

  • 对于第 1, 3, 5, 7 号元素:[2, 9, 23, 15] 进行排序,结果为 [2, 9, 15, 23]。
  • 对于第 2, 4, 6, 8 号元素:[7, 5, 29, 19] 进行排序,结果为 [5, 7, 19, 29]。

结果数组:[2, 5, 9, 7, 15, 19, 23, 29, 31]

第三步:间隔为 1 时,进行最后一次插入排序,得到最终排序结果:[2, 5, 7, 9, 15, 19, 23, 29, 31]

void ShellSort(int* arr, int n)
{int gap = n;while (gap > 1){gap = gap / 3;for (int i = 0; i < n - gap; i++){int end = i;int tmp = arr[end + gap];while (end >= 0){if (arr[end] > tmp){arr[end + gap] = arr[end];end -= gap;}else{break;}}arr[end + gap] = tmp;}}
}

归并排序

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

void _MergeSort(int* arr, int left, int right, int* tmp)
{//分开if (left >= right){return;}int mid = (left + right) / 2;_MergeSort(arr, left, mid, tmp);_MergeSort(arr, mid + 1, right, tmp);//合并int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;int index = begin1;while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] < arr[begin2]){tmp[index++] = arr[begin1++];}else{tmp[index++] = arr[begin2++];}}//要么begin1越界,要么begin2越界while (begin1 <= end1){tmp[index++] = arr[begin1++];}while (begin2 <= end2){tmp[index++] = arr[begin2++];}//将tmp数据拷贝回arr当中for (int i = left; i <= right; i++){arr[i] = tmp[i];}
}void MergeSort(int* arr, int n) 
{int* tmp = (int*)malloc(sizeof(int) * n);_MergeSort(arr, 0, n - 1, tmp);free(tmp);
}

1. 时间复杂度: O(nlogn)

2. 空间复杂度: O(n)

计数排序

计数排序⼜称为鸽巢原理,是对哈希直接定址法的变形应⽤。

操作步骤:

1)统计相同元素出现次数

2)根据统计的结果将序列回收到原来的序列中

void CountSort(int* arr, int n)
{int max = arr[0];int min = arr[0];for (int i = 1; i < n; i++){if (arr[i] > max){max = arr[i];}if(arr[i] < min){min = arr[i];}}int range = max - min + 1;int* count = (int*)malloc(sizeof(int) * range);if (count == NULL){perror("malloc fail!");exit(1);}//初始化range中的数据为0memset(count, 0, range * sizeof(int));//统计数组中每个数据出现的个数for (int i = 0; i < n; i++){count[arr[i] - min]++;}int index = 0;//取count中的数据往arr中放for (int i = 0; i < range; i++){while (count[i]--){arr[index++] = i + min;}}
}

这五种排序的优缺点

1. 快速排序:

  • 优点:
    • 平均时间复杂度为 O(nlog⁡n)O(n \log n)O(nlogn),非常高效。
    • 原地排序,不需要额外的存储空间(递归调用栈除外)。
    • 在实际应用中表现出色,尤其对大数据集。
  • 缺点:
    • 最坏情况下时间复杂度为 O(n2)O(n^2)O(n2),特别是在数据有序时。
    • 不稳定,可能改变相同元素的相对顺序。
    • 对递归栈空间有要求,可能导致栈溢出。

2. 希尔排序:

  • 优点:
    • 改进了插入排序,通过使用间隔的分组排序减少了移动次数。
    • 时间复杂度一般为 O(n1.3−n2)O(n^{1.3} - n^{2})O(n1.3−n2),对于中等规模的数据表现良好。
    • 原地排序,不需要额外空间。
  • 缺点:
    • 不是稳定排序,相同元素可能打乱顺序。
    • 性能依赖于选取的增量序列,难以分析其最优时间复杂度。
    • 实现相对复杂。

3. 直接插入排序:

  • 优点:
    • 实现简单,适合少量元素时的排序。
    • 稳定排序,保持相同元素的相对顺序。
    • 对于几乎有序的数组非常高效(接近 O(n)O(n)O(n))。
  • 缺点:
    • 时间复杂度为 O(n2)O(n^2)O(n2),对大规模数据效率低。
    • 需要频繁的元素移动,尤其是当数据无序时。

4. 归并排序:

  • 优点:
    • 时间复杂度为 O(nlog⁡n)O(n \log n)O(nlogn),在最坏情况下仍能保持高效。
    • 稳定排序,保持相同元素的相对顺序。
    • 可用于链表等不连续存储的数据结构。
    • 非原地排序,但可以用外部排序实现超大数据集的排序。
  • 缺点:
    • 需要额外的空间 O(n)O(n)O(n),对于内存敏感的应用不是很理想。
    • 实现相对复杂。

5. 计数排序:

  • 优点:
    • 时间复杂度为 O(n+k)O(n+k)O(n+k),对数据范围 kkk 相对较小的整数数据集非常高效。
    • 稳定排序,保持相同元素的相对顺序。
    • 不涉及比较,适用于一些特殊的场景,如成绩排名等。
  • 缺点:
    • 需要额外的存储空间 O(k)O(k)O(k),当数据范围大时,空间消耗可能过高。
    • 只能处理整数或离散类型数据,无法处理浮点数或复杂类型数据。
    • 对于数据范围远大于数据量的情况,效率不高。

这篇关于【数据结构】——原来排序算法搞懂这些就行,轻松拿捏的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1151800

相关文章

闲置电脑也能活出第二春?鲁大师AiNAS让你动动手指就能轻松部署

对于大多数人而言,在这个“数据爆炸”的时代或多或少都遇到过存储告急的情况,这使得“存储焦虑”不再是个别现象,而将会是随着软件的不断臃肿而越来越普遍的情况。从不少手机厂商都开始将存储上限提升至1TB可以见得,我们似乎正处在互联网信息飞速增长的阶段,对于存储的需求也将会不断扩大。对于苹果用户而言,这一问题愈发严峻,毕竟512GB和1TB版本的iPhone可不是人人都消费得起的,因此成熟的外置存储方案开

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c

poj 3974 and hdu 3068 最长回文串的O(n)解法(Manacher算法)

求一段字符串中的最长回文串。 因为数据量比较大,用原来的O(n^2)会爆。 小白上的O(n^2)解法代码:TLE啦~ #include<stdio.h>#include<string.h>const int Maxn = 1000000;char s[Maxn];int main(){char e[] = {"END"};while(scanf("%s", s) != EO

秋招最新大模型算法面试,熬夜都要肝完它

💥大家在面试大模型LLM这个板块的时候,不知道面试完会不会复盘、总结,做笔记的习惯,这份大模型算法岗面试八股笔记也帮助不少人拿到过offer ✨对于面试大模型算法工程师会有一定的帮助,都附有完整答案,熬夜也要看完,祝大家一臂之力 这份《大模型算法工程师面试题》已经上传CSDN,还有完整版的大模型 AI 学习资料,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

hdu 1285(拓扑排序)

题意: 给各个队间的胜负关系,让排名次,名词相同按从小到大排。 解析: 拓扑排序是应用于有向无回路图(Direct Acyclic Graph,简称DAG)上的一种排序方式,对一个有向无回路图进行拓扑排序后,所有的顶点形成一个序列,对所有边(u,v),满足u 在v 的前面。该序列说明了顶点表示的事件或状态发生的整体顺序。比较经典的是在工程活动上,某些工程完成后,另一些工程才能继续,此时

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)