【C语言】实现插入排序、选择排序、冒泡排序、希尔排序、归并排序、堆排序、快速排序

  • Post author:
  • Post category:其他





前言

排序是很重要的一个算法,在现实生活中也处处都有它的身影,诸如网上购物按价格排序、学校考试名次的排名……排序应用的场景都很多,本章介绍

插入排序、选择排序、冒泡排序、希尔排序、归并排序、堆排序、快速排序




一、冒泡排序

冒泡排序的思想很简单,无非就是分成多趟排序,每一趟排序按照需求(升序或降序)把最大或最小的元素排到最后面,每趟排序从第一个元素开始两两比较,大的或小的往后挪,排完一趟后最后一个元素是有序的了,所以下一趟排序最后一个元素不参与排序。


代码实现:

//交换两个数
void Swap(int* e1, int* e2)
{
	int tmp = *e1;
	*e1 = *e2;
	*e2 = tmp;
}

// 冒泡排序
void BubbleSort(int* a, int n)
{
	//n个元素,需要走n-1趟
	for (int i = 0; i < n - 1; i++)
	{
		//每趟
		for (int j = 0; j < n - 1 - i; j++)
		{
			if (a[j] > a[j + 1])
			{
				Swap(&a[j], &a[j + 1]);
			}
		}
	}
}

这里排的是升序,注意每趟的比较次数,第一趟比较次数为

n-1

次,第一趟排完已经有一个元素是有序了,所以第二趟待排序的个数就是

n-1

个,而

n-1

个比较

n-1-1

次就行了,利用每趟走完

i++

这一点,

j<n-1-i

正正好。


代码优化:

对于冒泡排序还是可以优化一下的,在待排序的数据是有序的状态下,以上代码还是会乖乖的一步一步比较走完,白白浪费了时间,所以我们来优化一下,假设一个变量

flag



1

,如果在某一趟中,没有进入交换循环,那也就表示当前数据是有序的了,接下来也就不再需要比较了,如若无序,

flag

变为

0

,则进入下一趟再把

flag

置为

1


优化代码如下:

//交换两个数
void Swap(int* e1, int* e2)
{
	int tmp = *e1;
	*e1 = *e2;
	*e2 = 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)
			return;
	}
}



二、选择排序

选择排序的思想也很简单,按照升序或者降序,每次比较选择最小的元素或者最大的元素,然后与第一个元素交换,然后再比较选择次大或是次小的元素,与第二个元素交换,依此直至数据全都排序完成。


代码如下:

//交换两个数
void Swap(int* e1, int* e2)
{
	int tmp = *e1;
	*e1 = *e2;
	*e2 = tmp;
}

// 选择排序
void SelectSort1(int* a, int n)
{
	int begin = 0;
	int end = n - 1;
	while (begin < end)
	{
		//假设第一个元素的下标的最小的
		int mini = begin;
		for (int i = begin + 1; i <= end; i++)
		{
			if (a[i] < a[mini])
				mini = i;
		}
		//最小的元素与最大的元素交换
		Swap(&a[begin], &a[mini]);
		begin++;
	}
}

这里排的是升序,注意end的取值是

n-1

,因为下面

i

是从

begin+1

开始的,

end

如果取

n

的话,当

begin

为最后一个元素的下标时,此时的

i+1

会导致越界,所以

end

取值为

n-1

,当倒数第二个元素有序时,倒数第一个元素也就有序了。


代码优化:

这里如果我们排升序的话,每次只取最小的元素放在最前面,这样效率不高,所以我们每次取最小的元素和最大的元素,也就相当于一次就排好了两个元素,这样效率会高一些。


优化代码如下:

//交换两个数
void Swap(int* e1, int* e2)
{
	int tmp = *e1;
	*e1 = *e2;
	*e2 = tmp;
}

// 选择排序
void SelectSort(int* a, int n)
{
	//每趟选出最小的和最大的
	int begin = 0;
	int end = n - 1;
	while (begin < end)
	{
		//记录该躺最小的值的下标
		int mini = begin;
		//记录该躺最大的值的下标
		int maxi = begin;
		for (int i = begin + 1; i <= end; i++)
		{
			if (a[i] < a[mini])
				mini = i;
			if (a[i] > a[maxi])
				maxi = i;
		}
		Swap(&a[begin], &a[mini]);
		//预防该躺最大的值就在begin
		if (begin == maxi)
			maxi = mini;
		Swap(&a[end], &a[maxi]);
		begin++;
		end--;
	}
}

这里我们需要注意,下面两行代码的作用是为了防止当最大元素的下标与当前

begin

相等时,此时说明最大的元素是在

begin

位置,而我们的交换是先把最小的元素与

begin

位置的元素进行交换,此时最小的元素去到了

begin

位置,而

begin

原本保存了最大的元素,所以最大的元素又与

mini

位置的元素进行了交换,此时如果

maxi

位置要与

end

位置进行交换时,显然是不对的了,因为原本

maxi

位置的值已经被移动到了

mini

的位置,所以当

begin



maxi

相等时,在

mini

位置的值与

begin

位置的值进行了交换后,最大的值跑到了

mini

的位置下,那么

maxi

就应该等于

mini

,这样

maxi

再与

end

交换,此时才是正确的

//预防该躺最大的值就在begin
if (begin == maxi)
	maxi = mini;


错误举例:

1)此时

begin



maxi

是相等的,可以看到最大值是存储在下标为

0

处,然后走到红色框处,执行第一次交换,也就是最小值与

begin

的交换。

在这里插入图片描述

2)此时最小值已经与

begin

交换,可以看到最小值已经移动到了正确的位置,但是原本的最大值却移动到了下标为

9

处,那么此时

maxi

所指向的下标处存储的就不是最大值了,这样就会导致排序出错,所以当

maxi



begin

相等时,

maxi

应该等于

mini

,这样排序才正确。

在这里插入图片描述




三、插入排序

插入排序的思想并不难,无非就是插入一个数,然后判断该数的前面有多少个大于或小于该数的数,然后挪动这些数,最后将该数再插入到合适的位置即可。


画图举例:

这是第一次排序,结合代码来看很容易理解,我们要排的是升序,循环条件是

end >= 0

,而此次的

end



0



tmp

存储

end

的下一位,当

tmp < end

时,将

end

移动到

end + 1

处,

end–

,再判断与

tmp

的大小关系,而当有一次

tmp > end

处的值时,该处就是

tmp

此次排序的合适位置,最终

tmp

所处的位置都是当前的

end + 1

处。

在这里插入图片描述


代码如下:

// 插入排序
void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}



四、希尔排序

希尔排序的大体思想是,先进行预排序,让较大的数先尽快的到达后面,而在进行的多趟预排后,数据已经趋近于有序了,最后一趟,也就是相当于插入排序了,但是此时的数据已经趋于有序,所以最后一趟会很快。


图例:


在这里插入图片描述

其中

gap

可以理解为间距。


代码如下:

// 希尔排序
void ShellSort(int* a, int n)
{
	int gap = n;
	//gap > 1(预排序)
	//gap == 1(直接插入排序)
	while (gap > 1)
	{
		gap /= 2;
		//gap = gap / 3 + 1; //需要+1才能确保某些情况的最后一次为1
		//单趟排序
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (a[end] > tmp)
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}



五、归并排序

归并排序思想大致是这样子的,假设数据的左半部分是有序的,右半部分也是有序的,就对它的左半部分和右半部分比较,每次比较取小的把小的插入到一个数组中,然后当左半部分或者右半部分中的某一部分的元素已经取完了,剩下的直接依次插入数组的后面即可。



递归

递归求解就是把数据分为左半部分和右半部分,而左半部分又分为左半部分和右半部分……依此下去直到当前区间元素只剩一个元素或者一个元素都不剩,一个元素默认有序,然后排好序后往回归,最后回到最开始的左半部分和右半部分,此时左半部分和右半部分就都有序了,那么整体比较之后数据就有序了。


图例:

在这里插入图片描述


代码如下:

//归并递归
void ReMergeSort(int* a, int begin, int end, int* tmp)
{
	//本次递归只有一个元素
	if (begin >= end)
		return;
	//本次数组的中间下标
	int mid = begin + (end - begin) / 2;
	//递归左
	ReMergeSort(a, begin, mid, tmp);
	//递归右
	ReMergeSort(a, mid + 1, end, tmp);
	//左起点
	int begin1 = begin;
	//左终点
	int end1 = mid;
	//右起点
	int begin2 = mid + 1;
	//右终点
	int end2 = end;
	//记录tmp存储数据下标
	int i = begin;
	//判断两个待比较的有序数组都没走完
	//升序排序
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] <= a[begin2])
			tmp[i++] = a[begin1++];
		else
			tmp[i++] = a[begin2++];
	}
	//没走完的直接赋值
	while (begin1 <= end1)
		tmp[i++] = a[begin1++];
	while (begin2 <= end2)
		tmp[i++] = a[begin2++];
	//有序的部分copy回原数据
	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}

//归并排序
void MergeSort(int* a, int n)
{
	//tmp数组用于数组的中转
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (!tmp)
	{
		perror("malloc()");
		exit(-1);
	}
	ReMergeSort(a, 0, n - 1, tmp);
	free(tmp);
	tmp = NULL;
}

这里当递归到区间内的元素小于等于

1

时为结束标志,然后把已经比较过的有序的部分存入临时的数组

tmp

,再把

tmp

的数据拷贝回原数组,需要注意每一次存入

tmp

的数据都要拷贝回原数组。



非递归

非递归采用变量

gap

来控制每次左右两组的元素个数,

gap



1

开始,即左右两组中每组只有

1

个元素时默认其是有序的,然后归并,此时每两个都是有序的,然后每次

gap 乘等 2

,循环结束条件为

gap

大于等于

n

(元素个数)。


图例:


在这里插入图片描述

以上是

n

的个数为

2

的次方数时的情况,但如果

n

的个数不是

2

的次方数,则会出现越界情况,如下图所示:

在这里插入图片描述

通过上图可以看到,但

n



10

时,不是

2

的次方数,此时就出现了越界情况,所以为了避免越界情况的发生,我们通过加上

if

语句即可解决。


代码如下:

//归并非递归
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (!tmp)
	{
		perror("malloc()");
		exit(-1);
	}

	//左右每组待比较的元素个数
	int gap = 1;

	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			//左半部分
			int begin1 = i;
			int end1 = i + gap - 1;
			//右半部分
			int begin2 = i + gap;
			int end2 = i + 2 * gap - 1;

			//处理越界
			if (end1 >= n)
			{
				break;
			}
			else if (begin2 >= n)
			{
				break;
			}
			else if (end2 >= n)
			{
				end2 = n - 1;
			}
			
			//判断两个待比较的有序数组都没走完
			//升序排序
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
					tmp[i++] = a[begin1++];
				else
					tmp[i++] = a[begin2++];
			}
			//没走完的直接赋值
			while (begin1 <= end1)
				tmp[i++] = a[begin1++];
			while (begin2 <= end2)
				tmp[i++] = a[begin2++];
			//有序的部分copy回原数据
			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		gap *= 2;
	}

	free(tmp);
	tmp = NULL;
}

可能越界的情况有



种:


  • end1

    越界,直接

    break

    ,不用再比较,已经是有序的了。

  • begin2

    越界,直接

    break

    ,不用再比较,已经是有序的了。

  • end2

    越界,不能直接

    break

    ,因为

    begin2

    没有越界,那么

    begin2

    此区间还是有元素的,要和前面的

    begin1-end1

    此区间比较,所以

    end2

    赋值为

    n-1

    ,即最后一个元素的下标。


越界图示:


在这里插入图片描述




六、堆排序

堆排序的大体思想是,根据情况先建好堆,需要排升序就建大堆,需要排降序就建小堆,建好堆之后,根据堆的性质,堆顶元素是当前堆中最大或者最小的元素(根据建的是大堆还是小堆而定),把堆顶的元素移至最后,除去已经移至最后的元素,用剩下的元素重新建堆,以此往复,直至数据有序。


堆的定义:

//堆
typedef struct Heap
{
	int* data;//本体
	int size;//当前存储元素个数
	int capacity;//总个数
}Heap;


建堆:

这里排升序,所以建的是大堆,这里建堆采用向下调整建堆,代码如下:

//向下建堆
//n -- 堆总大小
//father -- 当前堆中最后一个元素的父节点
void AdjustDwon(Heap* hp, int n, int father)
{
	int child = (father * 2) + 1;
	while (child < n)
	{
		//判断右孩子是否存在,且右孩子小于左孩子
		if (child + 1 < n && hp->data[child + 1] > hp->data[child])
		{
			child++;
		}
		if (hp->data[father] >= hp->data[child])
		{
			break;
		}
		else
		{
			Swap(&hp->data[father], &hp->data[child]);
			father = child;
		}
		child = (father * 2) + 1;
	}
}

// 堆的构建
void HeapCreate(Heap* hp, int* a, int n)
{
	assert(hp);
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (!tmp)
	{
		perror("malloc()");
		exit(-1);
	}
	hp->data = tmp;
	hp->capacity = hp->size = n;
	memcpy(hp->data, a, n * sizeof(int));
	//向下建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDwon(hp, hp->size, i);
	}
}


建堆图示:


在这里插入图片描述

建好堆之后,就把堆顶的元素与堆中的最后一个元素交换,此时堆的总个数减一,剩下的元素再进行建大堆,最后再重复之前的步骤即可。


代码如下:

// 堆排序
void HeapSort(int* a, int n)
{
	Heap heap;
	//建堆
	HeapCreate(&heap, a, n);
	int tmp = n;
	for (int i = tmp; i > 0;)
	{
		Swap(&heap.data[0], &heap.data[i - 1]);
		i--;
		AdjustDwon(&heap, i, 0);
	}
}



七、快速排序

快速排序大体思想是,先定义好一个

key



key

的作用是用来分割两段区间,如果排的是升序,那么

key

的左边的数据都是比其小的,

key

的右边都是比其大的,

key

的选择一般是数组中最左边的值或者是最右边的值,这里选最左边的值做

key

,如果选最左边的值做

key

,此时定义两个变量

left



right



left = begin



right = end – 1

(数组中最后一个元素),那么一开始就要

right

先走,反之,

right

先走,遇到比

key

小的值时停下,然后

left

开始走,遇到比

key

大的值时停下,然后交换两值,再继续走,直到

left >= right

时结束,然后此时再让

key



left

或者

right

的值交换,此时的

key

就到了合适正确的位置,而

key

也分割出了两段区间,再对这两段区间进行上述的重复操作,最后就排好序了。



递归



hoare版本


图示(单趟):


在这里插入图片描述


代码如下:

//交换
void Swap(int* e1, int* e2)
{
	int tmp = *e1;
	*e1 = *e2;
	*e2 = tmp;
}

// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{
	int begin = left;
	int end = right;
	//三数取中优化
	int mid = GetMid(a, begin, end);
	Swap(&a[mid], &a[left]);
	int keyi = left;
	while (left < right)
	{
		while (left < right && a[right] >= a[keyi])
			right--;
		while (left < right && a[left] <= a[keyi])
			left++;
		Swap(&a[left], &a[right]);
	}
	Swap(&a[left], &a[keyi]);
	keyi = left;
	return keyi;
}

//快速排序
void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	//小区间优化——大幅减少递归次数,大幅减少开辟的栈帧,预防栈溢出
	//到后面的层数运用直接插入排序
	if ((right - left + 1) <= 10)
	{
		//因为每次直接插入排序的起点都一定是0,所以加上left
		//因为left到right为闭区间,所以元素个数要加上1
		InsertSort(a + left, (right - left + 1));
	}
	else
	{
		int keyi = PartSort1(a, left, right);
		QuickSort(a, left, keyi - 1);
		QuickSort(a, keyi + 1, right);
	}
}



挖坑法


图示(单趟):


在这里插入图片描述


代码如下:

//交换
void Swap(int* e1, int* e2)
{
	int tmp = *e1;
	*e1 = *e2;
	*e2 = tmp;
}

// 快速排序挖坑法
int PartSort2(int* a, int left, int right)
{
	int begin = left;
	int end = right;
	//三数取中优化
	int mid = GetMid(a, begin, end);
	Swap(&a[mid], &a[left]);
	int keyi = left;
	//坑位
	int hole = left;
	while (begin < end)
	{
		while (left < right && a[right] >= a[keyi])
			right--;
		Swap(&a[hole], &a[right]);
		hole = right;
		while (left < right && a[left] <= a[keyi])
			left++;
		Swap(&a[hole], &a[left]);
		hole = left;
	}
	Swap(&a[hole], &a[left]);
	return hole;
}

//快速排序
void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	//小区间优化——大幅减少递归次数,大幅减少开辟的栈帧,预防栈溢出
	//到后面的层数运用直接插入排序
	if ((right - left + 1) <= 10)
	{
		//因为每次直接插入排序的起点都一定是0,所以加上left
		//因为left到right为闭区间,所以元素个数要加上1
		InsertSort(a + left, (right - left + 1));
	}
	else
	{
		int keyi = PartSort2(a, left, right);
		QuickSort(a, left, keyi - 1);
		QuickSort(a, keyi + 1, right);
	}
}



前后指针法

前后指针法与前两种有一点区别,还是最左边做

key

,然后定义

prev

等于

begin



cur

等于

begin+1

,当

cur

的值小于

key

时,必须先

prev++

,再交换

prev



cur

的值,然后当

cur

的值大于等于

key

时,只让

cur++

即可,最后当

cur >= n

(元素个数)时结束循环。


图示(单趟):


在这里插入图片描述


代码如下:

//交换
void Swap(int* e1, int* e2)
{
	int tmp = *e1;
	*e1 = *e2;
	*e2 = tmp;
}

// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{
	//三数取中优化
	int mid = GetMid(a, left, right);
	Swap(&a[mid], &a[left]);
	int prev = left;
	int cur = left + 1;
	int keyi = left;
	while (cur <= right)
	{
		//找到比a[keyi]小的数,且仅当prev不等于cur时进行交换,因为当prev等于cur时交换同一个数没有意义
		if (a[cur] < a[keyi] && ++prev != cur)
			Swap(&a[prev], &a[cur]);
		cur++;
	}
	Swap(&a[prev], &a[keyi]);
	keyi = prev;
	return keyi;
}

//快速排序
void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	//小区间优化——大幅减少递归次数,大幅减少开辟的栈帧,预防栈溢出
	//到后面的层数运用直接插入排序
	if ((right - left + 1) <= 10)
	{
		//因为每次直接插入排序的起点都一定是0,所以加上left
		//因为left到right为闭区间,所以元素个数要加上1
		InsertSort(a + left, (right - left + 1));
	}
	else
	{
		int keyi = PartSort3(a, left, right);
		QuickSort(a, left, keyi - 1);
		QuickSort(a, keyi + 1, right);
	}
}


优化:

当前写的快速排序面对两个问题:

  • 元素有序或趋于有序的情况下,这样

    left

    找大、

    right

    找小时会一骑绝尘,就是停不下来了,可能一直走到头都找不到,或者要走到很后面才找到,这样效率不高,所以第一个优化就是三数取中,分别取当前数据的

    begin



    end

    、和

    mid

    位置的数,取三个数中的中间数做

    key

    ,这样即使有序或趋于有序也不会出现最坏的情况。
  • 因为之前实现的快速排序是递归实现的,所以当元素个数很大时,会产生很深的递归,势必会开辟很多的栈帧,最终导致栈溢出,而快速排序的大部分递归都在后面的几层,所以我们设置当

    (right – left + 1) <= 10

    时就不再递归了,这里

    +1

    是因为传参传的是闭区间,开区间不需要

    +1

    ,当满足条件时,调用插入排序,效率会高些。


优化代码如下:



1)三数取中:

// 快速排序优化——三数取中(规避快排最坏情况,即待排序的数组有序)
// 三数取中即取既不是最大也不是最小的三个数的中间的那一个
int GetMid(int* a, int begin, int end)
{
	//快排——当数据中有大量的重复元素
	//int mid = begin + rand() % (end - begin);
	//正常情况
	int mid = begin + (end - begin) / 2;
	if (a[begin] > a[mid])
	{
		//begin最大,end最小
		if (a[mid] > a[end])
			return mid;
		//end最大,mid最小
		else if (a[end] > a[begin])
			return begin;
		//begin最大,mid最小
		else
			return end;
	}
	//a[mid] >= a[begin]
	else
	{
		//mid最大,end最小
		if (a[begin] > a[end])
			return begin;
		//end最大,begin最小
		else if (a[end] > a[mid])
			return mid;
		//min最大,begin最小
		else
			return end;
	}
}



2)小区间优化

//小区间优化——大幅减少递归次数,大幅减少开辟的栈帧,预防栈溢出
	//到后面的层数运用直接插入排序
	if ((right - left + 1) <= 10)
	{
		//因为每次直接插入排序的起点都一定是0,所以加上left
		//因为left到right为闭区间,所以元素个数要加上1
		InsertSort(a + left, (right - left + 1));
	}



非递归

快速排序的非递归实现需要借助栈这个数据结构的帮助,先把当前的

begin



end

入栈,然后第一趟排好后,

key

到了合适的位置,用

key

的下标来分割左右两段区间,分别是左边

[begin,key-1]

,右边

[key+1,end]

,将其分别入栈,每次取栈顶的两个元素来决定此次排序的区间,直至栈为空结束。(

注意栈是先入后出,入栈需注意!


栈的代码:



1)头文件

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>

// 支持动态增长的栈

//便于后续栈数据类型的修改
typedef int STDataType;

typedef struct Stack
{
	STDataType* data; //本体
	int top;	   // 栈顶
	int capacity;  // 容量 
}Stack;

// 初始化栈 
void StackInit(Stack* ps);
// 入栈 
void StackPush(Stack* ps, STDataType data);
// 出栈 
void StackPop(Stack* ps);
// 获取栈顶元素 
STDataType StackTop(Stack* ps);
// 获取栈中有效元素个数 
int StackSize(Stack* ps);
// 检测栈是否为空
bool StackEmpty(Stack* ps);
// 销毁栈 
void StackDestroy(Stack* ps);



2)源文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"newstack.h"

// 初始化栈 
void StackInit(Stack* ps)
{
	assert(ps);
	STDataType* tmp = (STDataType*)malloc(sizeof(STDataType) * 5);
	if (!tmp)
	{
		perror("malloc()");
		exit(-1);
	}
	ps->data = tmp;
	ps->capacity = 5; 
	ps->top = 0; //top指向栈顶的上一个
}

// 入栈 
void StackPush(Stack* ps, STDataType data)
{
	//空间满了,扩容
	if (ps->top == ps->capacity)
	{
		STDataType* tmp = (STDataType*)realloc(ps->data, sizeof(STDataType) * ps->capacity + 5);
		if (!tmp)
		{
			perror("realloc()");
			exit(-1);
		}
		ps->data = tmp;
		ps->capacity += 5;
	}
	//空间没满,正常入栈
	ps->data[ps->top++] = data;
}

// 出栈 
void StackPop(Stack* ps)
{
	assert(ps);
	assert(ps->top);
	ps->top--;
}

// 获取栈顶元素 
STDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(ps->top);
	return ps->data[ps->top - 1];
}

// 获取栈中有效元素个数 
int StackSize(Stack* ps)
{
	assert(ps);
	return ps->top;
}

// 检测栈是否为空
bool StackEmpty(Stack* ps)
{
	assert(ps);
	return ps->top == 0;
}

// 销毁栈 
void StackDestroy(Stack* ps)
{
	assert(ps);
	free(ps->data);
	ps->data = NULL;
	ps->capacity = ps->top = 0;
}


快速排序非递归代码如下:

// 快速排序 非递归实现
// 用栈实现
void QuickSortNonR(int* a, int left, int right)
{
	//创建栈
	Stack stack;
	//初始化栈
	StackInit(&stack);

	//先入最开始的区间下标
	//先入右边的,再入左边的,则出栈时top的数据为begin
	StackPush(&stack, right);
	StackPush(&stack, left);

	//直到栈为空时不存在区间则结束
	while (!StackEmpty(&stack))
	{
		int begin = StackTop(&stack);
		StackPop(&stack);
		int end = StackTop(&stack);
		StackPop(&stack);

		int keyi = PartSort3(a, begin, end);

		//判断区间内是否有至少两个值
		if (keyi + 1 < end)
		{
			//栈先入后出
			//先入end再入begin
			StackPush(&stack, end);
			StackPush(&stack, keyi + 1);
		}
		//判断区间内是否有至少两个值
		if (begin < keyi - 1)
		{
			//栈先入后出
			//先入end再入begin
			StackPush(&stack, keyi - 1);
			StackPush(&stack, begin);
		}
	}
	//销毁栈
	StackDestroy(&stack);
}



总结

至此,本章结束,谢谢观看!



版权声明:本文为m0_62714628原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。