快速排序(非递归)以及归并排序的递归与非递归

快速排序的非递归算法:

递归次数太多的缺陷:极端情况下(栈帧深度太深)会导致栈溢出,即使程序代码正确(递归的深度足够深时,空间不足,就会导致栈溢出),因此在实际应用中通常情况下是利用非递归算法实现。

递归改成非递归:1.直接改循环(简单)2.借助数据结构栈模拟递归过程(复杂)

首先要建立一个栈,直接引用之前建立过的栈即可,用栈来模拟左右递归的过程,由于栈是先进的后出,为了先排左边的,就得先将被分割后的右边下标存入栈中,当左边的下标被提取完之后,说明左边已经有序,接着取出右边的下标进行单趟排序即可。以此类推:

快速排序非递归代码:

void QuickSortNoneR(int* a, int n)
{
	ST st;
	StackInit(&st);
	StackPush(&st, n - 1);
	StackPush(&st, 0);
	while (!StackEmpty(&st))
	{
		int left = StackTop(&st);
		StackPop(&st);
		int right = StackTop(&st);
		StackPop(&st);
		int keyIndex = PartSort1(a, left, right);
		//[left,keyIndex-1] keyIndex [keyIndex+1,right]
		if (keyIndex + 1 < right)
		{
			StackPush(&st, right);
			StackPush(&st, keyIndex + 1);
		}
		if (keyIndex - 1 > left)
		{
			StackPush(&st, keyIndex - 1);
			StackPush(&st, left);
		}
	}
	StackDestory(&st);
}

Stack.h

#pragma once
#include<stdio.h>
#include<stdbool.h>
#include<assert.h>
#include<stdlib.h>
typedef int STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;//栈顶
	int capacity;
}ST;
//初始化
void StackInit(ST* ps);
//销毁栈
void StackDestory(ST* ps);
//栈顶插入数据(入栈)
void StackPush(ST* ps,STDataType x);
//出栈
void StackPop(ST* ps);
STDataType StackTop(ST* ps);
int StackSize(ST* ps);
bool StackEmpty(ST* ps);

 Stack.c

#define _CRT_SECURE_NO_WARNINGS
#include"Stack.h"
//初始化
void StackInit(ST* ps)
{
	assert(ps);
	ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
	if (ps->a == NULL)
	{
		printf("malloc fail\n");
		exit(-1);//不是正常结束
	}
	ps->capacity = 4;
	ps->top = 0;//意味着top指向的是栈顶元素的下一位,若为-1,则top指向的是栈顶元素
}
//销毁栈
void StackDestory(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->top = 0; 
}
//栈顶插入数据(入栈)
void StackPush(ST* ps, STDataType x)
{
	assert(ps);
	if (ps->top == ps->capacity)
	{
		STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);//不是正常结束
		}
		else
		{
			ps->a = tmp;
			ps->capacity *= 2;
		}
	}
	ps->a[ps->top] = x;
	ps->top++;
}
//出栈
void StackPop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);//栈空了,调用pop,直接中止程序报错
	ps->top--;
}
//返回栈顶元素
STDataType StackTop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);
	return ps -> a[ps->top - 1];
}
//返回栈中数据的数量
int StackSize(ST* ps)
{
	assert(ps);
	return ps->top;
}
//判断是否为空
bool StackEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

我对于快速排序的非递归说的不太详细,大家想要多理解的话点这里

快速排序 非递归icon-default.png?t=N7T8https://blog.csdn.net/qq_41890240/article/details/124110735,这个大大写的非常详细,这里我就不细说啦!膜拜膜拜!

 

归并排序的递归: 

归并排序与之前练习过的合并两个有序数组相似,将两个有序数组合并为同一个有序数组,最终的有序数组为临时创建的数组,归并排序的前提条件是分开的左右两个数据有序,由于随机给的数据不太可能是左右两边都有序,此时我们就可以想到将左右两边的数组不断地分组,知道一个数据为一组,可以看成是有序的,不断拆分的过程就是不断递归的过程。

接下来的问题就是如何将左右两个有序数据合并为一个有序的数组。

从两个数组的第一个元素开始比较,把小的放在新数组的第一个位置上,小的数组索引++,再接着与另一个数组的第一个元素比较,仍是把小的放在新数组上,以此类推即可。

这里的难点是递归的理解,递归中止的条件是,数组被分成了只有一个数据的数组,即左右下标相同,一次递归结束后(左边)只有一个数据,会进入上一层递归进行下一步操作,即找到右边的一个数据,接着进行有序数组的合并,接着将在临时数组已经排好序的两个数据拷贝到原数组中即可。

代码如下:

//归并排序
void _MergeSort(int* a, int left, int right,int* tmp)
{
	if (left >= right)
		return;
	int mid = (left + right) >> 1;
	//假设[left,mid][mid+1,right]有序,就可以使用归并排序
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid + 1, right, tmp);
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int index = left;
	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);
}

归并排序的非递归算法:

先设置一个gap为1,gap为要排序的每个数组(被分为若干份)的个数,以下图为例,将6和3看成一组,将其进行有序数组的排序,放在一个新数组中,后面的数依次排序并拷贝到新数组中,一轮排序完成后,将新数组的数拷贝到原数组中,接着将gap变为2,即3 6,5 2两个数组排序成2 3 5 6拷贝至新数组中,以此类推,,最终合并为一个1有序数组。

代码如下:

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			int index = i;
			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 j = 0; j < n; j++)
		{
			a[j] = tmp[j];
		}
		gap *= 2;
	}
	free(tmp);
}

 里面对于两个有序数组合并为一个有序数组问题与归并排序思想相同,排序部分的代码也与递归相同,只是将递归变成循环而已。但是这个代码有一个缺陷,就是如果当数组个数为奇数,最终总会有一个不能像其他数据一样有与其相同个数的数组排序如下:

由于gap每次都*2,也有可能右半区间数据个数不够,因此需要适当修正一下。

 当然,左半区间也可能出现个数不够的情况,这时左半部分就不会进行排序,并不会拷贝到新数组中,这时将新数组中的数据拷贝到原数组最后几个左区间就会是随机值,因此将新数组的数据拷贝到原数组中时,需要将排过序的数组拷贝至原数组中。

//归并过程中右半区间可能不存在
if (begin2 >= n)
	break;
// 归并区间右半部分区间算多了,修正一下
if (end2 >= n)
	end2 = n - 1;

拷贝:

//拷贝
			for (int j = i; j < i + end2; j++)
			{
				a[j] = tmp[j];
			}

修改后的代码:

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			//归并过程中右半区间可能不存在
			if (begin2 >= n)
				break;
			// 归并区间右半部分区间算多了,修正一下
			if (end2 >= n)
				end2 = n - 1;
			int index = i;
			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 j = i; j <= end2; j++)
			{
				a[j] = tmp[j];
			}
		}
		
		
		gap *= 2;
	}
	free(tmp);
}

这样子无论数组元素个数为多少都可以排序成功啦!

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您解答这个问题。 首先,堆排序快速排序归并排序和计数排序都是常见的排序算法,它们的实现方式各不相同。下面分别介绍它们的C++实现。 1. 堆排序(Heap Sort) 堆排序是一种选择排序,它利用堆的性质进行排序。堆是一个完全二叉树,其中每个节点的值都大于或等于其子节点的值(最大堆),或者小于或等于其子节点的值(最小堆)。在堆排序中,我们将待排序数组构建成一个最大堆,然后将堆顶元素(即最大值)与数组最后一个元素交换,然后重新调整堆,重复执行此操作,直到整个数组有序。 下面是堆排序的C++实现: ```c++ void heapify(int arr[], int n, int i) { int largest = i; // 初始化最大值为根节点 int l = 2 * i + 1; // 左子节点索引 int r = 2 * i + 2; // 右子节点索引 // 如果左子节点比最大值大,则更新最大值索引 if (l < n && arr[l] > arr[largest]) largest = l; // 如果右子节点比最大值大,则更新最大值索引 if (r < n && arr[r] > arr[largest]) largest = r; // 如果最大值不是根节点,则交换根节点和最大值,然后归调整堆 if (largest != i) { swap(arr[i], arr[largest]); heapify(arr, n, largest); } } void heapSort(int arr[], int n) { // 构建最大堆 for (int i = n / 2 - 1; i >= 0; i--) heapify(arr, n, i); // 依次取出堆顶元素,放到数组末尾,并重新调整堆 for (int i = n - 1; i >= 0; i--) { swap(arr[0], arr[i]); heapify(arr, i, 0); } } ``` 2. 快速排序(Quick Sort) 快速排序是一种分治排序,它利用分治的思想将大问题分解成小问题,然后归地解决小问题。在快速排序中,我们选择一个枢轴元素(一般选择第一个元素),将数组分为两个部分,左边部分的元素均小于枢轴元素,右边部分的元素均大于枢轴元素。然后分别对左右两部分归执行此操作,直到整个数组有序。 下面是快速排序的C++实现: ```c++ int partition(int arr[], int low, int high) { int pivot = arr[low]; // 枢轴元素 int i = low, j = high; while (i < j) { // 从右往左找第一个小于枢轴元素的位置 while (i < j && arr[j] >= pivot) j--; // 从左往右找第一个大于枢轴元素的位置 while (i < j && arr[i] <= pivot) i++; // 交换左右两个元素 if (i < j) swap(arr[i], arr[j]); } // 将枢轴元素放到正确的位置上 arr[low] = arr[i]; arr[i] = pivot; return i; } void quickSort(int arr[], int low, int high) { if (low < high) { int pivot = partition(arr, low, high); quickSort(arr, low, pivot - 1); quickSort(arr, pivot + 1, high); } } ``` 3. 归并排序(Merge Sort) 归并排序也是一种分治排序,它将待排序数组分成两部分,分别排序,然后合并成一个有序数组。在归并排序中,我们先将数组分为两半,分别对左半部分和右半部分归执行归并排序,然后将两个有序子数组合并成一个有序数组。 下面是归并排序的C++实现: ```c++ void merge(int arr[], int l, int m, int r) { int n1 = m - l + 1; int n2 = r - m; // 创建临时数组 int L[n1], R[n2]; // 将左半部分复制到临时数组 L for (int i = 0; i < n1; i++) L[i] = arr[l + i]; // 将右半部分复制到临时数组 R for (int i = 0; i < n2; i++) R[i] = arr[m + 1 + i]; // 合并左右两个有序子数组 int i = 0, j = 0, k = l; while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k] = L[i]; i++; } else { arr[k] = R[j]; j++; } k++; } // 将剩余的元素复制到数组中 while (i < n1) { arr[k] = L[i]; i++; k++; } while (j < n2) { arr[k] = R[j]; j++; k++; } } void mergeSort(int arr[], int l, int r) { if (l < r) { int m = l + (r - l) / 2; mergeSort(arr, l, m); mergeSort(arr, m + 1, r); merge(arr, l, m, r); } } ``` 4. 计数排序(Counting Sort) 计数排序是一种比较排序,它利用元素出现的次数来确定每个元素在有序数组中的位置。在计数排序中,我们先统计每个元素出现的次数,然后计算每个元素在有序数组中的位置,最后将元素放到正确的位置上。 下面是计数排序的C++实现: ```c++ void countingSort(int arr[], int n) { int max_val = 0; for (int i = 0; i < n; i++) { if (arr[i] > max_val) max_val = arr[i]; } // 创建计数数组 int count[max_val + 1] = {0}; // 统计每个元素出现的次数 for (int i = 0; i < n; i++) count[arr[i]]++; // 计算每个元素在有序数组中的位置 for (int i = 1; i <= max_val; i++) count[i] += count[i - 1]; // 将元素放到正确的位置上 int output[n]; for (int i = n - 1; i >= 0; i--) { output[count[arr[i]] - 1] = arr[i]; count[arr[i]]--; } // 将有序数组复制到原数组中 for (int i = 0; i < n; i++) arr[i] = output[i]; } ``` 以上就是堆排序快速排序归并排序和计数排序的C++实现,希望能对您有所帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值