排序之交换排序


前言

所谓交换,是指根据序列中两个元素关键字的比较结果来对换这两个记录在序列中的位置。
交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。即将数组中两个元素进行比较,如果前者大于后者,就让两个元素交换位置。

一、冒泡排序

1、冒泡排序基本思想

冒泡排序是一种非常容易理解的排序,其基本思想就是:从前往后(从后往前)两两比较相邻元素的值,若为逆序(即 A[i-1] > A[i] ),则交换它们,直到序列比较完。我们称它为第一趟冒泡,结果是将最大的元素交换到待排序列的最后一个位置(或将最小的元素交换到待排序列的第一个位置),关键字最大的元素如石头一般下沉置水底(或关键字最小的元素如气泡一般逐渐往上"漂浮"直至"水面"。下一趟冒泡时,前一趟确定的最小元素不再参与比较,每趟冒泡的结果是把序列中的最大元素(或最小元素)放到了序列的最终位置…这样最多做n-1趟冒泡就能把所以元素排好序。
例如有如下一个数组。
在这里插入图片描述
进行第一次冒泡排序时,会将最大的元素向后调。
在这里插入图片描述
第二遍冒泡排序就将次大的元素向后调,每次冒泡排序都能确定一个最大值(最小值)的最终位置,随着元素的位置排好序,未排序的元素一趟冒泡排序时比较的次数也越来越少。

void BubbleSort(int* arr, int n)
{
	int i = 0;
	int j = 0;
	//冒泡排序每一趟都可以确定一个最大值(最小值),所以要排序n个元素,只需n-1次即可,因为最后一位不用排序。
	for (i = 0; i < n - 1; i++)
	{
		//设置一个标识,如果数据已经有序,则冒泡排序第一遍不会发生元素交换
		int exchange = 0;
		//每一趟冒泡排序都会确定一个值的位置,所以比较的次数也越来越少。
		for (j = 0; j < n - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				Swap(&arr[j], &arr[j + 1]);
				exchange = 1;
			}
		}
		//如果标识的值没有改变,说明元素已经有序,直接退出函数即可。
		if (exchange == 0)
		{
			break;
		}
	}
}

2、冒泡排序的效率

冒泡排序最优的情况是数据已经有序,此时冒泡排序时间复杂度为O(N),而插入排序在数据已经有序时也为O(N),但是当数据接近有序或者局部有序时,冒泡排序就不是最优了,而此时插入排序还是最优的情况,所以综合情况下插入排序的效率比冒泡排序高一些。
1.冒泡排序是一种非常容易理解的排序
2.时间复杂度:O(N^2)
3.空间复杂度:O(1)
4.稳定性:稳定

二、快速排序 – hoare版本

1、快速排序基本思想

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
例如有如下一个数组。
在这里插入图片描述
我们选出一个数组元素为key,然后记录其下标为keyi,一般选择最左边或者最右边的值。然后再设置两个变量left和right,用来记录数组最左边和最右边的下标。此时另keyi=left,即将key的值为数组最左边的值。
在这里插入图片描述

然后从右边right开始向前遍历数组,找到第一个小于key的值的下标后,然后从左边开始向后遍历数组,找到第一个大于key的值得下标,然后将这两个元素得值交换。

在这里插入图片描述
在这里插入图片描述
然后right继续向前遍历数组,left继续向后遍历数组,重复上面的操作。
在这里插入图片描述
在这里插入图片描述
直到left和right相遇,此时left指的是比key小的值,所以此时将left和keyi位置的元素交换。然后将keyi=left,则此时keyi下标的左边都是小于key值的元素,keyi下标右边都是大于key值的元素。
在这里插入图片描述
在这里插入图片描述
此时就完成了一趟快速排序,如果我们记录数组开头和结尾下标为begin和end,此时我们就可以递归将数组中[begin,keyi-1]和[keyi+1,end]区域内的数据进行上述的操作,这样一直递归下去,就会使数组变为有序。
在这里插入图片描述

2、快速排序代码实现

我们可以先写一趟快速排序的代码。keyi用来记录key的下标。

void QuickSort(int* arr, int n)
{
	//left记录数组元素开始下标
	int left = 0;
	//right记录数组元素最后的下标
	int right = n - 1;
	//令key的值为数组最左边的值。用keyi来记录key的下标
	int keyi = left;
	while (left < right)
	{
		//先让右边先走,找第一个小于key的元素
		while (arr[right] > arr[keyi])
		{
			--right;
		}
		//再让左边走,找第一个大于key的元素
		while (arr[left] < arr[keyi])
		{
			++left;
		}
		//然后让这两个元素交换,
		Swap(&arr[left], &arr[right]);
	}
	//此时left下标的元素的值还小于key,此时将keyi和left位置的值交换,令keyi为left
	//则此时key的下标为keyi,而且此时keyi左边的元素都小于key,keyi右边的元素都大于key。
	Swap(&arr[keyi], &arr[left]);
	keyi = left;
}

当我们分析时,这个代码当遇到下列这种情况时,会一直陷入死循环,因为arr[right]此时等于key的值,arr[left]此时等于key的值,都会跳出循环,然后将left和right位置的值交换后,还是arr[right]等于key的值,arr[left]等于key的值,所以还会跳出循环,交换left和right位置的值。所以此种情况就会死循环交换left和right的值。
在这里插入图片描述
上述情况我们只需在循环条件中加上=即可,即变为while(arr[right]>=key)和while(arr[left]<=key),这样如果遇到arr[right]与key相等时,也会直接跳过而继续向左走,直到遇到arr[right]<key时才停下。

void QuickSort(int* arr, int n)
{
	//left记录数组元素开始下标
	int left = 0;
	//right记录数组元素最后的下标
	int right = n - 1;
	//另key的值为数组最左边的值。
	int keyi = left;
	while (left < right)
	{
		//先让右边先走,找第一个小于key的元素
		while (arr[right] >= arr[keyi])
		{
			--right;
		}
		//再让左边走,找第一个大于key的元素
		while (arr[left] <= arr[keyi])
		{
			++left;
		}
		//然后让这两个元素交换,
		Swap(&arr[left], &arr[right]);
	}
	//此时left下标的元素的值还小于key,此时将keyi和left位置的值交换,令keyi为left
	//则此时key的下标为keyi,而且此时keyi左边的元素都小于key,keyi右边的元素都大于key。
	Swap(&arr[keyi], &arr[left]);
	keyi = left;
}

如果在遇到边界情况时,上述的代码也会出现问题,例如如下情况,此时在第一次while(arr[right]>=arr[keyi])时,right会一直减减,直到减为-1,而此时arr[-1]已经越界访问,会出现错误。所以在while判断中还需要加上left<right,以防止第一次while循环就将right减减越界或者将left加加越界。
在这里插入图片描述

void QuickSort(int* arr, int n)
{
	//left记录数组元素开始下标
	int left = 0;
	//right记录数组元素最后的下标
	int right = n - 1;
	//另key的值为数组最左边的值。
	int keyi = left;
	while (left < right)
	{
		//先让右边先走,找第一个小于key的元素
		while (left<right && arr[right] >= arr[keyi])
		{
			--right;
		}
		//再让左边走,找第一个大于key的元素
		while (left<right && arr[left] <= arr[keyi])
		{
			++left;
		}
		//然后让这两个元素交换,
		Swap(&arr[left], &arr[right]);
	}
	//此时left下标的元素的值还小于key,此时将keyi和left位置的值交换,令keyi为left
	//则此时key的下标为keyi,而且此时keyi左边的元素都小于key,keyi右边的元素都大于key。
	Swap(&arr[keyi], &arr[left]);
	keyi = left;
}

这样上述的代码就完成了一次快速排序。当完成一次快速排序后,此时下标为keyi的位置的左边的元素都小于key,keyi右边位置的元素都大于key。我们在此时递归调用上述操作,将keyi左边的元素也变为有序,然后将keyi右边的元素一步一步变为有序。

void QuickSort(int* arr, int begin,int end)
{
	//区间不存在,或者只有一个值时则不需要处理
	if (begin >= end)
	{
		return;
	}
	//left记录数组元素开始下标
	int left = begin;
	//right记录数组元素最后的下标
	int right = end;
	//另key的值为数组最左边的值。
	int keyi = left;
	while (left < right)
	{
		//先让右边先走,找第一个小于key的元素
		while (left<right && arr[right] >= arr[keyi])
		{
			--right;
		}
		//再让左边走,找第一个大于key的元素
		while (left<right && arr[left] <= arr[keyi])
		{
			++left;
		}
		//然后让这两个元素交换,
		Swap(&arr[left], &arr[right]);
	}
	//此时left下标的元素的值还小于key,此时将keyi和left位置的值交换,令keyi为left
	//则此时key的下标为keyi,而且此时keyi左边的元素都小于key,keyi右边的元素都大于key。
	Swap(&arr[keyi], &arr[left]);
	keyi = left;

	//递归将keyi左边和右边的元素进行相同操作
	QuickSort(arr, begin, keyi - 1);
	QuickSort(arr, keyi + 1, end);
}

3、为什么最左边值做key时,右边先走

为什么最左边值做key时,要让右边先走呢?
因为要保证相遇位置的值,比key要小或者就是key的值,然后此时执行Swap(&arr[keyi], &arr[left]); keyi = left;此时key值得下标为keyi,这样才满足keyi位置左边元素都小于key,keyi位置右边元素都大于key。
第一种情况:right先走,当right停下来时,left去遇到right,相遇位置就是right停下来的位置,而right停下来的位置就是比key要小的位置。
在这里插入图片描述
此时将left位置的值和keyi位置的值交换,然后让keyi=left,此时key值的下标为keyi,这样就满足keyi位置左边元素都小于key,keyi位置右边元素都大于key。
在这里插入图片描述
第二种情况:right先走,right没有找到比key要小的值,right去遇到了left,而此时的相遇位置是left上一轮停下来的位置,或者就是key的位置。此时满足keyi位置的右边都是大于key的元素。而keyi左边没有元素。
在这里插入图片描述

三、快速排序 – 挖坑法

1、快速排序 – 挖坑法基本思想

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,在学习了Hoare提出的快速排序后,有人对快速排序的实现进行了一些小的修改。
例如有如下一个数组。
在这里插入图片描述

先将第一个数据存放在临时变量key中,形成一个坑位,创建一个piti用来记录坑位的下标。然后将创建两个变量left和right记录数组首尾元素下标,分别从数组首尾开始遍历数组。
在这里插入图片描述

先从right开始向前遍历数组,如果数组中下标为right的元素的值小于key的话,就将该元素放到现在的坑位中,即将arr[piti] = arr[right],因为数组第一个数据已经存在key之中了,所以将数组第一个元素的数据覆盖也没有事,因为key之中已经保存了。然后将piti = right,即将right位置作为新的坑位。
在这里插入图片描述
然后再从left开始向后遍历数组,如果数组中下标为left的元素的值大于key的话,就将该元素放到现在的坑位中,即将arr[piti] = arr[left],因为上一步骤已经将arr[right]的值放到上一个坑位了,所以现在将arr[left]覆盖现在的坑位数据也不会造成arr[right]元素的丢失。然后将piti = left,即将现在left的位置作为新的坑位。
在这里插入图片描述
然后再将right减减,继续向前遍历数组,当遇到下标为right的元素的值小于key时,再次重复上面的操作,即将 arr[piti] = arr[right],然后将piti = right,即将现在right的位置作为新的坑位。

在这里插入图片描述
然后将left加加,继续向后遍历数组,当遇到下标为left的元素的值大于key时,再次重复上面的操作,即将
arr[piti] = arr[right],然后将piti = left,即将现在的left位置作为新的坑位。
在这里插入图片描述
然后再将right减减,继续向后遍历数组,当遇到下标为right的元素的值小于key时,再次重复上面的操作,即将 arr[piti] = arr[right],然后将piti = right,即将现在right的位置作为新的坑位。
在这里插入图片描述
此时再将left加加,然后left和right相遇。然后将现在的坑位放入key的值,即arr[piti] = key,此时可以看到piti位置左边的元素都小于key,piti位置右边的元素都大于key。此时挖坑法的一趟快速排序就完成了,然后接下来就是递归将piti左边的元素和右边的元素进行上述操作。最后就会将数组变为一个升序数组。
在这里插入图片描述

2、快速排序 – 挖坑法代码实现

因为上面Hoare法代码实现时,我们已经分析了while()中判断条件为什么要写为((left<right) && (arr[right]>=key)),并且挖坑法实现时也会遇到与Hoare法实现时相同的情况,所以我们需要接着按照while ((left<right) && (arr[right]>=key))这样写。

//快速排序 -- 挖坑法
int PartSort2(int* arr, int begin, int end)
{
	int left = begin;
	int right = end;
	int key = arr[begin];
	int piti = begin;
	while (left < right)
	{
		//右边开始找小于key的值
		while ((left<right) && (arr[right]>=key))
		{
			--right;
		}
		//找到了就将小的值放入坑位中,然后将right变为新的坑位
		arr[piti] = arr[right];
		piti = right;
		//左边开始找大于key的值
		while ((left<right) && (arr[left]<=key))
		{
			++left;
		}
		//如果找到了就将大的值放入坑位中,然后将left变为新的坑位
		arr[piti] = arr[left];
		piti = left;
	}
	//如果left和right相遇,就将坑位中放入key,此时就满足了piti位置左边的元素都小于key的值,piti位置右边的元素都大于key的值
	arr[piti] = key;
	return piti;
}

void QuickSort(int* arr, int begin,int end)
{
	//区间不存在,或者只有一个值时则不需要处理
	if (begin >= end)
	{
		return;
	}
	
	//获得返回的keyi值
	int keyi = PartSort2(arr, begin, end);
	//递归将keyi左边和右边的元素进行相同操作
	QuickSort(arr, begin, keyi - 1);
	QuickSort(arr, keyi + 1, end);
}

3、为什么最左边值做key时,右边先走

我们在实现Hoare法时已经分析过了,当最左边值做key时,右边先走保证了left和right相遇位置的值,比key要小或者就是key的值,然后此时将arr[piti] = tmp,此时key值得下标为piti,这样才满足piti位置左边元素都小于key,keyi位置右边元素都大于key。详细分析同Hoare法实现时分析类似。

四、快速排序 – 前后指针法

1、快速排序 – 前后指针法基本思想

快速排序还可以使用前后指针法来实现。
例如有如下一个数组。
在这里插入图片描述
先将数组最左边的值作为key,并且使用keyi来记录key的下标,然后创建指针指向数组开头,并且创建curr指针指向prev指针的后一个位置。
在这里插入图片描述
然后判断curr指针指向的数据是否小于key,如果小于,则使prev指针向后移一位,并且使curr指向的内容与prev指向的内容交换,然后curr指针++。而当prev向后移一位时,如果prev和curr指向同一个元素,则curr指向的内容与prev指向的内容交换就没有意义了,所以我们需要加一个判断,即(++prev) != (curr)。这样就不会使curr与prev指向同一元素时还交换curr指向的内容与prev指向的内容。
在这里插入图片描述
此时curr++后指向的元素还是小于key,则prev也++指向下一个元素,但是此时prev和curr指向的元素还是一个元素,所以还是不会发生元素交换。继续将curr++。
在这里插入图片描述
此时curr指向的元素小于key,先将prev++,然后判断发现prev和curr不是指向同一元素,所以交换prev和curr指向元素的内容。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
然后将curr继续++,向后遍历数组,直到遇到curr指向的元素比key小。而此时curr指向的元素的值又比key小了,所以此时要重复上述操作,即先让prev++指向后一个元素,然后经过判断prev和curr没有指向同一元素,所以交换prev指向元素和curr指向元素的内容。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
然后将curr继续++,向后遍历数组,直到遇到curr指向的元素比key小。而此时curr指向的元素的值又比key小了,所以此时要重复上述操作,即先让prev++指向后一个元素,然后经过判断prev和curr没有指向同一元素,所以交换prev指向元素和curr指向元素的内容。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
然后继续将curr++,向后遍历数组。此时curr指向数组的最后一个元素。
在这里插入图片描述
当curr指向数组最后一个元素时,说明数组已经遍历一遍,此时将prev指向的内容和keyi指向的内容交换。
在这里插入图片描述
在这里插入图片描述
然后再将keyi=prev,则此时keyi位置左边的元素都小于key,keyi位置右边的元素都大于key。到此为止就完成了一趟前后指针法快速排序,接下来就可以将keyi左边的元素和右边的元素递归进行上述操作,直到将数组变为有序。
在这里插入图片描述

2、快速排序 – 前后指针法代码实现

//快速排序法 -- 前后指针法
int  PartSort3(int* arr, int begin, int end)
{
	//使数组最左边的元素作为key
	int keyi = begin;
	//用prev记录数组首位置,curr记录prev下一个元素位置
	int prev = begin;
	int curr = prev + 1;
	//如果curr没有遍历到数组末尾就继续向后遍历
	while (curr <= end)
	{
		/*
		//如果curr此时指向的元素小于key
		if (arr[curr] < arr[keyi])
		{
			//先让prev向后移一位
			prev++;
			//然后判断prev是否和curr指向同一个元素,如不指向同一个元素,就让prev指向的内容和curr指向的内容交换
			if (prev != curr)
			{
				Swap(&arr[prev], &arr[curr]);
			}
		}
		*/

		//上面的代码判断可以简化为下面的
		//如果curr指向的元素小于key,并且++prev后不等于curr,就让prev和curr指向的内容交换
		if ((arr[curr] < arr[keyi]) && (++prev != curr))
		{
			Swap(&arr[prev], &arr[curr]);
		}
		//curr一直继续向后遍历数组
		curr++;
	}
	//最后将keyi指向的key和prev指向的内容交换,此时prev指向的就是key
	Swap(&arr[keyi], &arr[prev]);
	//然后将keyi指向key,即此时keyi指向的元素就是key
	keyi = prev;
	//将此时key的位置返回
	return keyi;
}

void QuickSort(int* arr, int begin,int end)
{
	//区间不存在,或者只有一个值时则不需要处理
	if (begin >= end)
	{
		return;
	}
	//Hoare法
	//int keyi = PartSort1(arr, begin, end);   

	//挖坑法
	//int keyi = PartSort2(arr, begin, end);

	//前后指针法
	int keyi = PartSort3(arr, begin, end); 
	//递归将keyi左边和右边的元素进行相同操作
	QuickSort(arr, begin, keyi - 1);
	QuickSort(arr, keyi + 1, end);
}

五、快速排序的优化

1、快速排序的效率分析

经过上面的分析,我们可以知道快速排序执行的效率和key值的选择有关,当快速排序的key值选取的为数据中的中间值时,这时快速排序的效率最高。此时函数递归的情况如图所示。
在这里插入图片描述
而当key值选取的为数据中最小值或最大值时,这时快速排序的效率最低。此时函数递归的情况如图所示。即当数据为有序或接近有序时,快速排序的效率最低,复杂度可以达到O(N^2)。
在这里插入图片描述

2、三数取中法优化快速排序

三数取中法就是取begin的值,end的值,还有mid = (begin + end) / 2的值,然后比较三个值,选取中间值作为快速排序的key值,这样key值就有很大概率不是数据中的最大值和最小值。

//三数取中法
int GetMidIndex(int* arr, int begin, int end)
{
	int mid = (begin + end) / 2;
	if (arr[begin] < arr[mid])
	{
		if (arr[mid] < arr[end])
		{
			return mid;
		}
		else if (arr[begin] < arr[end])
		{
			return end;
		}
		else
		{
			return begin;
		}
	}
	//a[begin] > a[mid]
	else
	{
		if (arr[mid] > arr[end])
		{
			return mid;
		}
		else if (arr[begin] < arr[end])
		{
			return begin;
		}
		else
		{
			return end;
		}
	}
}


//快速排序法 -- 前后指针法
int  PartSort3(int* arr, int begin, int end)
{
	//使数组最左边的元素作为key
	int keyi = begin;
	//用prev记录数组首位置,curr记录prev下一个元素位置
	int prev = begin;
	int curr = prev + 1;

	//利用三数选中法得到的key值为最大值最小值的概率更小
	int midi = GetMidIndex(arr, begin, end);
	//然后将此时keyi中的值与三数选中法的到的值进行交换,则此时keyi中的值就是三数选中法选出来的值。
	Swap(&arr[keyi], &arr[midi]);
	//如果curr没有遍历到数组末尾就继续向后遍历
	while (curr <= end)
	{
		/*
		//如果curr此时指向的元素小于key
		if (arr[curr] < arr[keyi])
		{
			//先让prev向后移一位
			prev++;
			//然后判断prev是否和curr指向同一个元素,如不指向同一个元素,就让prev指向的内容和curr指向的内容交换
			if (prev != curr)
			{
				Swap(&arr[prev], &arr[curr]);
			}
		}
		*/

		//上面的代码判断可以简化为下面的
		//如果curr指向的元素小于key,并且++prev后不等于curr,就让prev和curr指向的内容交换
		if ((arr[curr] < arr[keyi]) && (++prev != curr))
		{
			Swap(&arr[prev], &arr[curr]);
		}
		//curr一直继续向后遍历数组
		curr++;
	}
	//最后将keyi指向的key和prev指向的内容交换,此时prev指向的就是key
	Swap(&arr[keyi], &arr[prev]);
	//然后将keyi指向key,即此时keyi指向的元素就是key
	keyi = prev;
	//将此时key的位置返回
	return keyi;
}

void QuickSort(int* arr, int begin,int end)
{
	//区间不存在,或者只有一个值时则不需要处理
	if (begin >= end)
	{
		return;
	}
	//Hoare法
	//int keyi = PartSort1(arr, begin, end);   

	//挖坑法
	//int keyi = PartSort2(arr, begin, end);

	//前后指针法
	int keyi = PartSort3(arr, begin, end); 
	//递归将keyi左边和右边的元素进行相同操作
	QuickSort(arr, begin, keyi - 1);
	QuickSort(arr, keyi + 1, end);
}

3、小区间使用插入排序优化快速排序

如图为快速排序递归调用函数图,当递归划分生成小区间,区间比较小的时候,就不再递归划分去排序这个小区间,因为每次递归就之排序一两个数,但是需要调用一次函数,排序这两个数的开销太大了,此时可以考虑使用其他的排序来对小区间的数据进行处理。下面我们假设当区间小于10时,就不再使用递归排序小区间,而使用插入排序来排序小区间。

void QuickSort(int* arr, int begin,int end)
{
	//区间不存在,或者只有一个值时则不需要处理
	if (begin >= end)
	{
		return;
	}

	if ((end-begin) > 10)
	{
		//Hoare法
		//int keyi = PartSort1(arr, begin, end);   

		//挖坑法
		//int keyi = PartSort2(arr, begin, end);

		//前后指针法
		int keyi = PartSort3(arr, begin, end);
		//递归将keyi左边和右边的元素进行相同操作
		QuickSort(arr, begin, keyi - 1);
		QuickSort(arr, keyi + 1, end);
	}
	else
	{
		//当区间的元素小于等于10时,就直接使用插入排序来进行这个区间的排序
		//插入排序接收的是数组中要排序的起始位置,然后还有要排序数据的长度
		//所以第一个参数要为arr + begin,而第二个参数为要排序的数据长度
		InsertSort(arr + begin, end - begin + 1);
	}
	
}

六、非递归实现快速排序

用递归解决问题有时候会很巧妙,但是递归最大的问题就是当递归调用的函数次数太多时,即递归深度太深时,就会出现栈溢出现象,所以当遇到问题时,我们不但要能用递归方法解决,还需要知道怎么将递归改为非递归。
递归改非递归可以考虑使用下面的两种办法:
(1).直接改为循环 – 比如斐波那契数列、归并排序。
(2).用数据结构栈模拟递归过程。
下面我们就用数据结构模拟递归过程来将上面写的递归实现快速排序改为非递归实现快速排序。
在这里插入图片描述
然后求出此时key值的位置,此时将先将right和keyi+1入栈,再让keyi-1和left入栈。这样出栈时顺序就为left、keyi-1、keyi+1、right,即先处理区间[left,keyi-1],再处理区间[keyi+1,right]。
在这里插入图片描述

然后会将left当作left,keyi-1当作right,求出新的keyi1,然后就会先将right1和keyi1+1入栈,再将keyi1-1和left1入栈,然后下一次循环又会将left1作为left,keyi1-1作为right,求出新的keyi2,就像递归调用函数一样,先将左边的元素排好序,然后再排序右边元素。
在这里插入图片描述

void QuickSortNonR(int* arr, int begin, int end)
{
	//创建一个栈
	ST st;
	StackInit(&st);
	//将end和begin进栈
	StackPush(&st,end);
	StackPush(&st, begin);
	while (!StackEmpty(&st))
	{
		//将栈顶元素出栈
		int left = StackTop(&st);
		StackPop(&st);
		//将栈顶元素出栈
		int right = StackTop(&st);
		StackPop(&st);
		int keyi = PartSort3(arr, left, right);
		if (right > (keyi + 1))
		{
			StackPush(&st, right);
			StackPush(&st, keyi + 1);

		}
		if (left < (keyi-1))
		{
			StackPush(&st, keyi - 1);
			StackPush(&st, left);
		}
	}

	StackDestory(&st);
}

当我们实现用栈模拟递归的实现后,我们会发现其实使用队列也可以实现模拟递归。使用栈模拟的话顺序类似于二叉树中的先序遍历,而当使用队列时,则顺序类似于层序遍历。
在这里插入图片描述

//使用队列模拟递归
void QuickSortNonRQueue(int* arr, int begin, int end)
{
	//创建一个队列
	Queue qt;
	QueueInit(&qt);
	QueuePush(&qt, begin);
	QueuePush(&qt, end);
	while (!QueueEmpty(&qt))
	{
		int left = QueueFront(&qt);
		QueuePop(&qt);
		int right = QueueFront(&qt);
		QueuePop(&qt);

		int keyi = PartSort3(arr, left, right);
		if (left < (keyi-1))
		{
			QueuePush(&qt, left);
			QueuePush(&qt, keyi - 1);
		}
		if (right > (keyi + 1))
		{
			QueuePush(&qt, keyi + 1);
			QueuePush(&qt, right);
		}
	}
	QueueDestroy(&qt);
}


void QuickSortNonR(int* arr, int begin, int end)
{
	//使用栈模拟递归
	//QuickSortNonRStack(arr, begin, end);

	//使用队列模拟递归
	QuickSortNonRQueue(arr, begin, end);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值