初阶数据结构的各种排序方法——冒泡,直接插入,希尔,快排,选择,归并(C语言)

news2024/11/25 14:38:47

1.交换排序

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

1.1冒泡排序

例子:

int arr[] = { 3,1,6,5,2,7,9,8,4 };

基本思想:

冒泡排序就是将数组中的元素一一比较,然后交换位置,直到超出数组范围。一共n个数据,需要遍历n-1次,每遍历一次将其一一比较,遍历完一次就可以确定一个数据位置,所以每遍历完一次,下一次遍历的结束条件就要减一。

void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
//冒泡排序
void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int flag = 1;
		for (int j = 0; j < n - 1 - i; j++)
		{
			if (a[j] > a[j + 1])
			{
				Swap(&a[j], &a[j + 1]);
				flag = 0;
			}
		}
		if (flag == 1)
			break;
	}
}

这样一次遍历就将9排到指定位置,下一次遍历到9前面那个位置就可以了,所以每次遍历完下一次截止条件j-1-i,直到剩余第一个元素;这里有一个优化处理,定义flag为1,如果没有进入第二层循环,前一个数据都小于后一个了,说明已经有序了,就不用排了,直接跳出循环。冒泡排序时间复杂度O(n^2)。

1.2快速排序

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

快速排序实现主框架:

 //快速排序
 
void  QuickSort(int*a,int left,int right)
 {

     if(left >= right) {
         return;
      }
//_QuickSort⽤于按照基准值将区间[left,right)中的元素进⾏划分
 
     int keyi= _QuickSort(a, left, right);
     QuickSort(a, left, meet - 1);
     QuickSort(a, meet + 1, right);
}

将区间中的元素进⾏划分的 _QuickSort ⽅法主要有以下⼏种实现⽅式:

1.2.1 hoare版本

算法思路:创建左右指针,确定基准值;从右向左找出⽐基准值⼩的数据,从左向右找出⽐基准值⼤的数据,左右指针数据交换,进⼊下次循环;

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

注意:这里判断条件中,当a[keyi]=a[left];或者=a[right],不需要进行交换,因为当数据中有大量相等的数据是算法的效率会降低。

1.2.2挖坑法

思路:

创建左右指针。⾸先从右向左找出⽐基准⼩的数据,找到后⽴即放⼊左边坑中,当前位置变为新 的"坑",然后从左向右找出⽐基准⼤的数据,找到后⽴即放⼊右边坑中,当前位置变为新的"坑",结 束循环后将最开始存储的分界值放⼊当前的"坑"中,返回当前"坑"下标(即分界值下标)

// 快速排序挖坑法
int _QuickSort2(int* a, int right, int left)
{
	int hole = left;
	int key = a[hole];
	while (left < right)
	{
		while (left<right && a[right]>key)
		{
			right--;
		}
		a[hole] = a[right];
		hole = right;
		while (left<right && a[left]<key)
		{
			left++;
		}
		a[hole] = a[left];
		hole = left;
	}
	a[hole] = key;
	return hole;
}

1.2.3 lomuto前后指针

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

//双指针法
int _QuickSort3(int* a, int right, int left)
{
	int prev = left, cur = prev + 1;
	int keyi = left;
	while (cur <= right)
	{
		if (a[cur] < a[keyi] && ++prev != cur)
		{
			Swap(&a[cur], &a[prev]);
		}
		++cur;
	}
	Swap(&a[keyi], &a[prev]);
	return  prev;
}

时间复杂度:外层每一次相当于二分logn,内层遍历为n,所以时间复杂度为 O(nlogn)。

1.3⾮递归版本

⾮递归版本的快速排序需要借助数据结构:栈。(这里小编前面栈文章里面有详细讲解)

void QuickSortNonR(int* a, int left, int right)
{
	ST s;
	StackInit(&s);
	StackPush(&s, right);
	StackPush(&s, left);
	while (!StackEmpty(&s))
	{
		int begin = StackTop(&s);
		StackPop(&s);
		int end = StackTop(&s);
		StackPop(&s);
		int keyi=_QuickSort3(a, end, begin);
		if (keyi + 1 < end)
		{
			StackPush(&s, end);
			StackPush(&s, keyi+1);
		}
		if (keyi - 1 > begin)
		{
			StackPush(&s, keyi-1);
			StackPush(&s, begin);
		}
	}
	StackDestory(&s);
}

先将左右位置,入栈,先将右端入栈,左再入栈,进循环后去两次栈顶,利用取出来的两个位置,去找基准值,再将基准值,右边部分的两端点入栈,和左边两端点入栈,循环直到栈为空。

2. 插⼊排序

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

2.1直接插⼊排序

当插⼊第 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)
{
	for (int i = 0; i < n-1; i++)
	{
		int end = i;
		int temp = a[i+1];
		while (end >=0)
		{
			if (a[end] > temp)
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = temp;
	}
}

每次将end后面一个数,与end及之前的一一比较,当end位置的值大于tmp,就将end位置的值,向后移动,找到小于tmp的位置后就将tmp赋值给这个位置,完成一次循环,直到end走到n-1位置就完成排序了。

时间复杂度为O(n^2),实际上只有将数组为降序时,再排成升序,这种情况,才会达到O(n^2),实际运行效率是要低于O(n^2)。

2.2希尔排序(直接插入排序的优化)

希尔排序法⼜称缩⼩增量法。希尔排序法的基本思想是:

先选定⼀个整数(通常是gap=n/3+1),把待排序⽂件所有记录分成各组,所有的距离相等的记录分在同⼀组内,并对每⼀组内的记录进⾏排 序,然后gap=gap/3+1得到下⼀个整数,再将数组分成各组,进⾏插⼊排序,当gap=1时,就相当于直接插⼊排序。 它是在直接插⼊排序算法的基础上进⾏改进⽽来的,综合来说它的效率肯定是要⾼于直接插⼊排序算法的。

//希尔排序(直接插入排序的优化)
void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int temp = a[i + gap];
			while (end >= 0)
			{
				if (a[end] > temp)
				{
					a[end + gap] = a[end];
					end-=gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = temp;
		}
	}
}

这里gap每次划分也要有一定规划,例如gap/2划分的组多,每次比较的次数少,gap/8划分的组少,每组要比较的次数较多;一般取值gap/3。

希尔排序是对直接插⼊排序的优化。 2. 当 gap > 1 时都是预排序,⽬的是让数组更接近于有序。当gap == 1,数组已经接近有序的了,这样就会很快。这样整体⽽⾔,可以达到优化的效果。

希尔排序的时间复杂度估算:外层循环的时间复杂度可以直接给出为:即 O(log2n) 或者O(log3 n) ,O(logn),内层循环:假设⼀共有n个数据,合计gap组,则每组为n/gap个;在每组中,插⼊移动的次数最坏的情况下为 1 +2+3+....+( n/gap-1) ,⼀共是gap组,因此:

总计最坏情况下移动总数为:gap*[1 +2+3+....+( n/gap-1))];

gap取值有(以除3为例):n/3 n/9 n/27 ...... 2 1

当gap为n/3时,移动总数为:n/3*(1+2)=n;

当gap为n/9时,移动总数为:n/9*(1+2+3+....+8)=4n;

最后⼀躺,gap=1即直接插⼊排序,内层循环排序消耗为n

可以得出以下曲线:

因此,希尔排序在最初和最后的排序的次数都为n,即前⼀阶段排序次数是逐渐上升的状态,当到达 某⼀顶点时,排序次数逐渐下降⾄n,⽽该顶点的计算暂时⽆法给出具体的计算过程,大概约等于n^1.3。

3.选择排序

选择排序的基本思想:

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

3.1直接选择排序

在元素集合 array[i]--array[n-1] 中选择关键码最⼤(⼩)的数据元素,若它不是这组元素中的最后⼀个(第⼀个)元素,则将它与这组元素中的最后⼀个(第⼀个)元素交换,在剩余的 array[i]--array[n-2] ( array[i+1]--array[n-1] ) 集合中,重复上述步 骤,直到集合剩余1个元素。

//选择排序
void SelectSort(int* a, int n)
{
	int left = 0;
	int right = n - 1;
	while (right > left)
	{
		int min = left;
		int max = left;
		for (int i = left + 1; i < right; i++)
		{
			if (a[min] > a[i])
			{
				min = i;
			}
			if (a[max] < a[i])
			{
				max = i;
			}
		}
		if (max == left)
			max = min;
		Swap(&a[left], &a[min]);
		Swap(&a[right], &a[max]);
		right--;
		left++;
	}
}

注意:这里当第一个元素是max是第一个元素时,min先于第一个元素交换后就找不到max了

,所以这里要进行特殊处理一下,将max位置放到min位置。

时间复杂度:两层循环,内层开起来是找到max,min,是两次,但其实只循环了一次,所以时间复杂度O(n^2)。

3.2堆排序

堆排序(Heapsort)是指利⽤堆积树(堆)这种数据结构所设计的⼀种排序算法,它是选择排序的⼀ 种。它是通过堆来进⾏选择数据。需要注意的是排升序要建⼤堆,排降序建⼩堆。(基本思想在小编前面的文章,堆排序和Top—K中有详细讲述)

//堆排序
void AdjustDwon(int* a, int n, int root)
{
	int parent = root;
	int child = 2 * parent+1;
	while (child < n)
	{
		if (child + 1 < n && a[child] < a[child + 1])
		{
			child += 1;
		}
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = 2 * parent+1;
		}
		else
		{
			break;
		}
	}
}

void HeapSort(int* a, int n)
{
	int parent = (n-1-1 )/2;
	while (parent >= 0)
	{
		AdjustDwon(a, n, parent);
		parent--;
	}
	int size = n - 1;
	while (size > 0)
	{
		Swap(&a[0], &a[size]);
		AdjustDwon(a, size, 0);
		size--;
	}
}

4.归并排序

归并排序算法思想:

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

void _MergeSort(int* a, int left, int right, int* tmp)
{
	if (left >= right)
	{
		return;
	}
	int mid = left+(right-left) / 2;
	_MergeSort(a, left, mid , tmp);
	_MergeSort(a, mid+1, right, tmp);
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int index = begin1;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[index++] = a[begin1++];
		}
		else
		{
			tmp[index++] = a[begin2++];
		}
	}
	while (begin1 <= end1)
	{
		tmp[index++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[index++] = a[begin2++];
	}
	for (int i = left; i <= right; i++)
	{
		a[i] = tmp[i];
	}
}

void MergeSort(int* a, int n)
{
	int* tmp=(int*)malloc(sizeof(int) * n);
	_MergeSort(a, 0, n - 1, tmp);
	free(tmp);
}

算法时间复杂度:外层是一个相当于二分,即logn,内层是一个遍历n,所以算法时间复杂度为O(nlogn)。

5.排序性能比较

这里通过排十万组数据,可以大概观察到各排序算法的效率。

void TestOP()
{
	srand(time(0));
	const int N = 100000;
    int* a1 = (int*)malloc(sizeof(int) * N);
    int* a2 = (int*)malloc(sizeof(int) * N);
    int* a3 = (int*)malloc(sizeof(int) * N);
    int* a4 = (int*)malloc(sizeof(int) * N);
    int* a5 = (int*)malloc(sizeof(int) * N);
    int* a6 = (int*)malloc(sizeof(int) * N);
    int* a7 = (int*)malloc(sizeof(int) * N);
    for (int i = 0; i < N; ++i)
    {
        a1[i] = rand();
        a2[i] = a1[i];
        a3[i] = a1[i];
        a4[i] = a1[i];
        a5[i] = a1[i];
        a6[i] = a1[i];
        a7[i] = a1[i];
    }
    int begin1 = clock();
    InsertSort(a1, N);
    int end1 = clock();

    int begin2 = clock();
    ShellSort(a2, N);
    int end2 = clock();

    int begin3 = clock();
    SelectSort(a3, N);
    int end3 = clock();

    int begin4 = clock();
    HeapSort(a4, N);
    int end4 = clock();

    int begin5 = clock();
    QuickSort(a5, N - 1,0 );
    int end5 = clock();

    int begin6 = clock();
    MergeSort(a6, N);
    int end6 = clock();

    int begin7 = clock();
    BubbleSort(a7, N);
    int end7 = clock();

    printf("InsertSort:%d\n", end1 - begin1);
    printf("ShellSort:%d\n", end2 - begin2);
    printf("SelectSort:%d\n", end3 - begin3);
    printf("HeapSort:%d\n", end4 - begin4);
    printf("QuickSort:%d\n", end5 - begin5);
    printf("MergeSort:%d\n", end6 - begin6);
    printf("BubbleSort:%d\n", end7 - begin7);
    free(a1);
    free(a2);
    free(a3);
    free(a4);
    free(a5);
    free(a6);
    free(a7);
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

qt QFileInfo详解

1、概述 QFileInfo是Qt框架中用于获取文件信息的工具类。它提供了与操作系统无关的文件属性&#xff0c;如文件的名称、位置&#xff08;路径&#xff09;、访问权限、类型&#xff08;是否为目录或符号链接&#xff09;等。此外&#xff0c;QFileInfo还可以获取文件的大小、创…

Charles抓包_Android

1.下载地址 2.破解方法 3.安卓调试办法 查看官方文档&#xff0c;Android N之后抓包要声明App可用User目录下的CA证书 3.1.在Proxy下进行以下设置&#xff08;路径Proxy->Proxy Settings&#xff09; 3.1.1.不抓包Windows&#xff0c;即不勾选此项&#xff0c;免得打输出不…

软件压力测试有多重要?北京软件测试公司有哪些?

软件压力测试是一种基本的质量保证行为&#xff0c;它是每个重要软件测试工作的一部分。压力测试是给软件不断加压&#xff0c;强制其在极限的情况下运行&#xff0c;观察它可以运行到何种程度&#xff0c;从而发现性能缺陷。 在数字化时代&#xff0c;用户对软件性能的要求越…

【Python】【数据可视化】【商务智能方法与应用】课程 作业一 飞桨AI Studio

作业说明 程序运行和题目图形相同可得90分&#xff0c;图形显示有所变化&#xff0c;美观清晰可适当加分。 import matplotlib.pyplot as plt import numpy as npx np.linspace(0, 1, 100) y1 x**2 y2 x**4plt.figure(figsize(8, 6))# yx^2 plt.plot(x, y1, -., labelyx^2,…

进程的调度(超详细解读)

在特别老的操作系统中&#xff0c;进程的调度是根据FIFO调度算法进行调度&#xff0c;先进先出式的调度&#xff0c;其实就是队列&#xff0c;但是不能很好的体现进程的优先级&#xff0c;在上节讲解的进程优先级&#xff0c;知道nice值范围是[-20&#xff0c;19]&#xff0c;然…

【初阶数据结构篇】链式结构二叉树(续)

文章目录 须知 &#x1f4ac; 欢迎讨论&#xff1a;如果你在学习过程中有任何问题或想法&#xff0c;欢迎在评论区留言&#xff0c;我们一起交流学习。你的支持是我继续创作的动力&#xff01; &#x1f44d; 点赞、收藏与分享&#xff1a;觉得这篇文章对你有帮助吗&#xff1…

【拥抱AI】如何让软件开发在保证数据安全的同时更加智能与高效?

第一、推动软件开发向更加智能化、高效化和创新化方向发展的策略 随着AI技术的不断进步&#xff0c;软件开发正朝着更加智能化、高效化和创新化的方向发展。要实现这一目标&#xff0c;企业需要采取一系列综合性的策略&#xff0c;从技术、管理、文化等多个层面入手。以下是一…

elementUI 点击弹出时间 date-picker

elementUI的日期组件&#xff0c;有完整的UI样式及弹窗&#xff0c;但是我的页面不要它的UI样式&#xff0c;点击的时候却要弹出类似的日期选择器&#xff0c;那怎么办呢&#xff1f; 以下是elementUI自带的UI风格&#xff0c;一定要一个输入框来触发。 这是我的项目中要用到的…

柯桥topik考级韩语培训【韩语干货】表存在的에和에게有什么区别?

相同点 都可以接在体词后&#xff0c;表示存在的地点、场所&#xff0c;以及所有者。 例如&#xff1a; 1&#xff09;여동생이 집에 있어요. 妹妹在家。 2&#xff09; 식당이 도서관 뒤에 있다. 食堂在图书馆后面。 3&#xff09; 언니에게 고급 화장품이 있다. 姐姐有高级…

使用 ABAP GIT 发生 IF_APACK_MANIFEST dump

错误重现 使用经典的 ABAP 系统运行 ZABAPGIT 或者 ZABAPGIT_STANDALONE然后添加在线或者离线项目点击 PullShort dump SYNTAX_ERROR Dump 界面&#xff1a; 解决方案 它发生在 CREATE OBJECT lo_manifest_provider TYPE (ls_manifest_implementation-clsname) 语句中。 该语…

多商户电商平台开发指南:基于直播带货系统源码的搭建方案详解

本篇文章&#xff0c;小编将详细解析如何利用直播带货系统源码&#xff0c;快速搭建一套多商户电商平台的解决方案。 一、直播带货系统在多商户电商平台中的应用价值 在多商户电商平台中&#xff0c;直播带货系统可以帮助商家&#xff1a; 1.增加用户互动 2.提升转化率 3.…

【TextIn:开源免费的AI智能文字识别产品(通用文档智能解析识别、OCR识别、文档格式转换、篡改检测、证件识别等)】

TextIn&#xff1a;开源免费的AI智能文字识别产品&#xff08;通用文档智能解析识别、OCR识别、文档格式转换、篡改检测、证件识别等&#xff09; 产品的官网&#xff1a;TextIn官网 希望感兴趣以及有需求的小伙伴们多多了解&#xff0c;因为这篇文章也是源于管网介绍才产出的…

(C++回溯算法)微信小程序“开局托儿所”游戏

问题描述 给定一个矩阵 A ( a i j ) m n \bm A(a_{ij})_{m\times n} A(aij​)mn​&#xff0c;其中 a i j ∈ { 1 , 2 , ⋯ , 9 } a_{ij}\in\{1,2,\cdots,9\} aij​∈{1,2,⋯,9}&#xff0c;且满足 ∑ i 1 m ∑ j 1 n a i j \sum\limits_{i1}^m\sum\limits_{j1}^na_{ij} i…

Java学习Day57:碧水金睛兽!(Spring Cloud微服务1.0)

1.微服务入门 (1).单体架构与分布式架构 单体架构&#xff1a; 将业务的所有功能集中在一个项目中开发&#xff0c;打成一个包部署优点&#xff1a; 架构简单、部署成本低 &#xff1b; 缺点&#xff1a; 耦合度高项目打包部署到Tomcat&#xff0c;用户直接访问。用户量增加后…

Golang | Leetcode Golang题解之第541题反转字符串II

题目&#xff1a; 题解&#xff1a; func reverseStr(s string, k int) string {t : []byte(s)for i : 0; i < len(s); i 2 * k {sub : t[i:min(ik, len(s))]for j, n : 0, len(sub); j < n/2; j {sub[j], sub[n-1-j] sub[n-1-j], sub[j]}}return string(t) }func min…

★ C++进阶篇 ★ C++11(上)

Ciallo&#xff5e;(∠・ω< )⌒☆ ~ 今天&#xff0c;我将和大家一起学习C11 ~ ​❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️ 澄岚主页&#xff1a;椎名澄嵐-CSDN博客 C基础篇专栏&#xff1a;★ C基础篇 ★_椎名澄嵐的博客-CSDN博客 C进阶篇专栏&#xff…

【Linux 28】应用层协议 - HTTPS

文章目录 &#x1f308; 一、HTTPS 相关概念⭐ 1. 什么是 HTTPS⭐ 2. 加密 & 解密 & 密钥⭐ 3. 常见的加密方式⭐ 4. 数据摘要 & 数据指纹⭐ 5. 初识数字签名 &#x1f308; 二、HTTPS 的加密方案探究⭐ 1. 方案一&#xff1a;只使用对称加密⭐ 2. 方案二&#xff…

qt QFileDialog详解

1、概述 QFileDialog是Qt框架中的一个对话框类&#xff0c;用于提供一个标准的文件选择对话框。它允许用户浏览文件系统&#xff0c;选择一个或多个文件或目录&#xff0c;以及指定文件名。QFileDialog支持本地文件系统和远程文件系统&#xff08;如通过FTP或SFTP访问的文件系…

C语言不同基本数据类型占用字节大小和取值范围

具体请前往&#xff1a;C语言各种基本数据类型字节大小和取值范围

Vue3:新特性详解

本文目录&#xff1a; 1.特性函数setup2.Ref 语法3.Reactive 函数4.Vue3 生命周期5.侦测变化 - watch6.Vue3的模块化开发7.弹窗类组件优化&#xff1a;Teleport8.异步组件优化&#xff1a;Suspense9.全局API优化 1.特性函数setup 1、setup函数是处于 生命周期函数 beforeCreate…