[C语言实现]快排归并非递归以及八大排序时空稳定性分析

本文介绍了快速排序和归并排序的非递归版本,通过栈实现,重点分析了两种算法的时间复杂度(O(NlogN)~O(N^2))、空间复杂度(O(logN)和O(N))以及稳定性。同时,对比了插入排序、希尔排序、选择排序、堆排序、冒泡排序等其他排序算法的特点。
摘要由CSDN通过智能技术生成


🥰作者: FlashRider

🌏专栏: 初阶数据结构

🍖知识概要:探讨八大排序的时空复杂度与稳定性,讲解快排和归并的非递归实现。

目录

快速排序-非递归

归并排序-非递归

八大排序分析


快速排序-非递归

快速排序的递归过程实际上可以看作二叉树,我们利用先序遍历或者层序遍历(栈或队列)来实现非递归即可。

这里直接用栈实现,对于一趟快排结束后,我们要排他的左右区间,所以我们把区间入栈就行了,但要注意入栈出栈的顺序,不要把区间的端点弄反了。

void QuickSortNonR(int* a, int begin, int end)//栈实现
{
	Stack st;
	StackInit(&st);
	StackPush(&st, end);
	StackPush(&st, begin);
	while(!StackEmpty(&st))
	{
		int left = StackTop(&st);
		StackPop(&st);
		int right = StackTop(&st);
		StackPop(&st);
		int key = left;
		int i = left, j = right;

		//此处用i j快排一趟 key随关键值下标改变而改变
        while(i < j)
	    {
    		while(i < j && a[j] >= a[key]) j--;
		    while(i< j&& a[i] <= a[key]) i++;
		    Swap(a[i], a[j]);
	    }
    	Swap(a[i], a[key]);
	    key = i;//修正key

		if(left < key - 1)//子区间入栈 可以下面的if交换顺序 不重要 
        //只要Push的时候的两个区间左右下标都对应前面的left right即可。
		{
			StackPush(&st, key - 1);
			StackPush(&st, left);
		}
		if(key + 1 < right)
		{
			StackPush(&st, right);
			StackPush(&st, key + 1);
		}
	}
	StackDestroy(&st);
}

归并排序-非递归

归并排序因为要先划分区间,所以过程是二叉树的后序遍历,因此用栈和队列不好去实现,所以我们选择用步长来不断分割区间,控制好边界条件,模拟非递归。

因为gap是不断×2的,所以对于一些序列长度来说划分的区间可能会越界,因此越界后要修正边界。

oid MergeSortNonR(int* a, int n)
{
	int gap = 1;
	int* tmp = (int*)malloc(sizeof(int) * n);
	while(gap < n)
	{
		for(int i = 0; i < n; i += 2*gap)
		{
			//分为[i, i + gap - 1] [i+gap, i + 2*gap - 1]
			int l1 = i, r1 = i+gap-1;
			int l2 = i+gap, r2 = i+2*gap-1;
			int k = l1;
			//越界修正边界
			if(r1 >= n)
			{
				r1 = n - 1;
				l2 = n;
				r2 = n - 1;
			}
			else if(l2 >= n)
			{
				l2 = n;
				r2 = n - 1;
			}
			else if(r2 >= n)
				r2 = n - 1;
			while(l1 <= r1 && l2 <= r2)
			{
				if(a[l1] < a[l2]) 
				{
					tmp[k++] = a[l1];
					l1++;
				}
				else
				{
					tmp[k++] = a[l2];
					l2++;
				}
			}
			while(l1 <= r1) tmp[k++] = a[l1++];
			while(l2 <= r2) tmp[k++] = a[l2++];
		}
		memcpy(a, tmp, n * sizeof(int));
		gap *= 2;
	}
	free(tmp);
}

八大排序分析

时间复杂度:时间复杂度简单来说就是对一个算法所用的时间量级的表示,比如一个算法,你输入n个数据,他要大概循环n次,执行n次关键语句,那它的时间复杂度就是O(n)。

空间复杂度:空间复杂度是算法需要的额外空间的表示,比如归并n个数据,他需要开一个大小为n的临时数组,因此空间复杂度就是O(n)。

稳定性:排序算法的稳定性简单来说就是对于两个相等的数据,排序前后他们的相对顺序不变。
比如 1 3 1 2 排序后 1 1 2 3。红包依然在橙色前面。

排序算法时间复杂度空间复杂度稳定性分析
插入排序O(N)~O(N^2)O(1)稳定对于有序序列插入排序的时间最短,接近O(N),对于完全倒序的序列时间复杂度为O(N^2)。因为是一个一个数据往前比较插入,所以相同数据不会交换,是稳定的。
希尔排序平均O(N^1.3)O(1)不稳定只需要记住平均复杂度就行,详细分析比较复杂,可以去查其他资料。因为是按照步长插排的,所以不一定稳定。
选择排序O(N^2)O(1)不稳定每次选择最大最小为n次循环,要选择n次,所以一共是n*n=n^2的时间复杂度,因为选择完后直接是按位置交换,而不考虑交换的时候谁大谁小,所以不稳定。 
堆排序O(NlogN)O(1)不稳定原序列建堆调整为logn,遍历为n,所以时间复杂度为nlogn。
冒泡排序O(N)~O(N^2)O(1)稳定相邻比较交换,所以稳定。优化之后,对于有序序列,遍历一次发现没有交换就会break,所以最好是O(N),最坏O(N^2)。
快速排序O(NlogN)~O(N^2)O(logN)不稳定递归需要开辟函数栈帧,非递归也需要栈或队列模拟,所以空间复杂度是logN。对于完全有序的序列来说快排时间复杂度会降低到N^2,此时可以使用取mid为key来优化。
归并排序O(NlogN)O(N)稳定归并需要有临时数组,所以空间复杂度O(N)。因为是两个序列比较,按大小顺序加入到临时数组中,所以是稳定的。

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
快速排序是一种常用的排序算法,它通过递归地将数组分成较小和较大的两个子数组来实现排序。下面是快速排序的C语言递归实现: ```c #include <stdio.h> // 交换两个元素的值 void swap(int* a, int* b) { int temp = *a; *a = *b; *b = temp; } // 将数组分成两部分,并返回分割点的索引 int partition(int arr[], int low, int high) { int pivot = arr[high]; // 选取最后一个元素作为基准值 int i = (low - 1); // 记录小于基准值的元素的索引 for (int j = low; j <= high - 1; j++) { // 如果当前元素小于等于基准值,则将其与小于基准值的元素交换位置 if (arr[j] <= pivot) { i++; swap(&arr[i], &arr[j]); } } swap(&arr[i + 1], &arr[high]); // 将基准值放到正确的位置上 return (i + 1); // 返回分割点的索引 } // 快速排序递归函数 void quickSort(int arr[], int low, int high) { if (low < high) { int pi = partition(arr, low, high); // 获取分割点的索引 // 对分割点左边的子数组进行递归排序 quickSort(arr, low, pi - 1); // 对分割点右边的子数组进行递归排序 quickSort(arr, pi + 1, high); } } // 打印数组 void printArray(int arr[], int size) { for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } printf("\n"); } int main() { int arr[] = {64, 25, 12, 22, 11}; int n = sizeof(arr) / sizeof(arr); printf("原始数组:\n"); printArray(arr, n); quickSort(arr, 0, n - 1); printf("排序后的数组:\n"); printArray(arr, n); return 0; } ``` 这段代码实现了快速排序算法的递归版本。首先定义了一个`swap`函数用于交换两个元素的值。然后定义了`partition`函数,它选择最后一个元素作为基准值,将数组分成两部分,并返回分割点的索引。接下来是`quickSort`函数,它通过递归调用自身来对分割点左右两个子数组进行排序。最后,在`main`函数中测试了快速排序算法

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值