【数据结构】——常见排序算法(演示图+代码+算法分析)

目录

1.  常见排序算法

1.2 稳定性

2.  常见排序算法的实现

2.1 插入排序

2.1.1基本思想

2.1.2代码

2.1.4算法分析

 2.2 希尔排序

2.2.1基本思想

2.2.2代码

2.2.3演示图

 2.2.4算法分析

2.3 选择排序

2.3.1基本思想

2.3.2代码

2.3.3演示图

2.3.4算法分析

2.4 堆排序

2.4.1基本思想

 2.4.2代码

2.4.3演示图

2.4.4算法分析 

 2.5 冒泡排序

2.5.1基本思想

2.5.2代码 

2.5.3演示图

 2.5.4算法分析

 2.6 快速排序

 2.6.1基本思想

2.6.2代码(递归实现)

2.6.3 演示图

2.6.4算法分析 

2.6.5三数取中

2.6.6挖坑法

2.6.7前后指针法 

2.6.8快排的非递归实现

 3.  完整代码:

 Sort.h

Stack.h 

Sort.c 

Stack.c 

 test.c

运行结果截图


1.  常见排序算法

【数据结构】——常见排序算法(演示图+代码+算法分析)_第1张图片

1.2 稳定性

稳定性指的是在排序过程中,相等元素的相对顺序是否会改变。如果排序算法是稳定的,则相等元素的相对位置在排序后会保持不变;如果排序算法是不稳定的,则相等元素的相对位置有可能会改变。

2.  常见排序算法的实现

2.1 插入排序

2.1.1基本思想

插入排序:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。它的思想是将数据分为已排序区和未排序区。例如,实际中玩扑克牌的时候,就用到了直接插入排序。

【数据结构】——常见排序算法(演示图+代码+算法分析)_第2张图片

2.1.2代码

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

2.1.3演示图

【数据结构】——常见排序算法(演示图+代码+算法分析)_第3张图片

首先,我们把第一个数据看作已排序区,剩余的数据看作未排序区。

然后,我们逐个地将未排序区的数据插入到已排序区的合适位置。插入的过程就像是打扑克牌时,我们将新抓到的牌插入到手中已排序好的牌中的正确位置。

具体来说,我们从第二个数据开始,将它与已排序区的最后一个数据比较。如果这个数据比最后一个数据小(或者大,具体情况由你决定),就把它插入到已排序区的合适位置,并将已排序区中的其他数据向后移动一个位置。

然后,我们继续取下一个未排序区的数据,再次与已排序区的数据进行比较并插入到合适位置。我们不断重复这个过程,直到所有的数据都被插入到已排序区。

通过这种方式,每个数据在插入过程中都会找到自己的位置,最终形成一个完整的有序序列。

总结起来,插入排序就是逐个将未排序的数据插入到已排序的合适位置,直到所有数据都被插入完成。

插入排序的优点是简单易懂,适用于小型数据集或已经部分有序的数据集。但对于大型乱序的数据集来说,插入排序的效率可能不如其他更高级的排序算法。

2.1.4算法分析

1. 元素集合越接近有序,直接插入排序算法的时间效率越高

2. 时间复杂度:O(N^2)

3. 空间复杂度:O(1),它是一种稳定的排序算法

4. 稳定性:稳定

 2.2 希尔排序

2.2.1基本思想

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成gap个组,所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后,取gap=gap/3 +1,重复上述分组和排序的工作。当到达gap=1时,所有记录在统一组内排好序。

2.2.2代码

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 tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}

}

2.2.3演示图

【数据结构】——常见排序算法(演示图+代码+算法分析)_第4张图片

  • 预排序时:
  • gap越大,大的数可以更快得到后面,小的可以更快得到前面,越不接近有序;
  • gap越小,数据跳动越慢,越接近有序。 

 2.2.4算法分析

  1. 希尔排序是对直接插入排序的优化。
  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就 会很快。这样整体而言,可以达到优化的效果。
  3. 时间复杂度O(N^1.3)
  4. 稳定性:不稳定

2.3 选择排序

2.3.1基本思想

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

2.3.2代码

 (这里做了一些改进,从待排序的数据元素中选出小的放到最前面,选出大的放到后面)

void SelectSort(int* a, int n)
{
	int begin = 0, end = n-1;
	while (begin < end)
	{
		int mini = begin, 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]);
		if (maxi == begin)
		{
			maxi = mini;
		}
		Swap(&a[end], &a[maxi]);
		--end;
		++begin;
	}
}

由于最小的元素下标begin可能会与最大的元素下标重合,那么在执行Swap(&a[begin], &a[mini]);语句之后,begin的值就会变成mini的值,如果接下来直接执行Swap(&a[end], &a[maxi]);那么排序就会出现错误,所以在它们之间加入了一个判断语句。

2.3.3演示图

(小的数据往前放)

【数据结构】——常见排序算法(演示图+代码+算法分析)_第5张图片

  1. 把第1个数据当作最小元素
  2. 遍历数组中在这个最小元素之后的数据,找出比这个元素还要小的元素
  3. 交换
  4. 重复上述过程 

2.3.4算法分析

  1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  2.  时间复杂度:O(N^2)
  3.  空间复杂度:O(1)
  4.  稳定性:不稳定

2.4 堆排序

2.4.1基本思想

  1. 构建最大堆(或最小堆):将待排序的数组看作一个完全二叉树,根据堆的定义,可以通过从最后一个非叶子节点开始,逐层向上进行调整,使得每个父节点的值大于(或小于)其子节点的值。这个过程称为构建最大堆(或最小堆)。

  2. 排序:将构建好的最大堆(或最小堆)中的根节点与数组的最后一个元素交换位置,即将最大值(或最小值)放到数组的末尾。然后,将剩下的 n-1 个元素重新构建成一个最大堆(或最小堆),再次将堆顶元素与当前的末尾元素交换位置。重复这个过程,直到所有的元素都排好序。 

  3. 升序,建大堆;降序,建小堆。

 2.4.2代码

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child a[child])
		{
			++child;
		}
		//孩子大于父亲,交换,继续向下调整
		//孩子小于父亲,则调整结束
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void HeapSort(int* a, int n)
{
	//向下调整--O(N)
	//升序,建大堆
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}

	//O(N*logN)
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

int main()
{
    int a[] = { 9,8,7,6,5,4,3,2,1,5,3,1,0,4,-1,-2,-1 };
	HeapSort(a, sizeof(a) / sizeof(int));
    //打印数组数据
    return 0;
}

2.4.3演示图

在这里我就不演示了,在之前写的博客中有:【数据结构】二叉树——堆_@简单就好的博客-CSDN博客

以最大堆为例,首先建立一个大根堆

在排序过程中,每次将堆顶元素与当前堆的最后一个元素进行交换后,最后一个元素不看做堆里面的元素,再通过对n-1个元素向下调整操作来维护堆的性质。
这样,每次交换之后,最大的元素就被放置在了数组的末尾位置。随着排序的进行,每次交换都会将当前堆中的最大值移动到数组的末尾,
所以最后每次的最后一个数是最大的。经过多次迭代,整个数组就会按照从小到大的顺序排序完成

2.4.4算法分析 

  1. 堆排序使用堆来选数,效率就高了很多。
  2.  时间复杂度:O(N*logN)
  3.  空间复杂度:O(1)
  4.  稳定性:不稳定 

 2.5 冒泡排序

2.5.1基本思想

通过相邻元素之间的比较和交换,逐渐将最大(或最小)的元素“冒泡”到正确的位置上。

2.5.2代码 

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void BubbleSort(int* a, int n)
{
	for (int j = 0; j < n; ++j)
	{
		int exchange = 0;
		for (int i = 1; i < n - j; ++i)
		{
			if (a[i - 1] > a[i])
			{
				Swap(&a[i - 1], &a[i]);
				exchange = 1;
			}
		}
		//一趟冒泡过程中,如果没有发生交换,说明已经有序
		if (exchange == 0)
		{
			break;
		}
	}

}

int main()
{
    int a[] = { 9,5,4,0,2,1,3,7,6,8};
	BubbleSort(a, sizeof(a) / sizeof(int));
    //打印数组数据
    return 0;
}

冒泡排序代码中,内循环表示一趟排序,即进行一趟冒泡排序;i < n - j 表示前n-j个元素进行冒泡排序;exchange表示如果在一趟排序后exhange=0,即没有对数据进行新的顺序改动,则有序。

2.5.3演示图

 【数据结构】——常见排序算法(演示图+代码+算法分析)_第6张图片

 2.5.4算法分析

  1. 冒泡排序是一种非常容易理解的排序
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定

 2.6 快速排序

 2.6.1基本思想

任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

2.6.2代码(递归实现)

//Hoare
int PartSort1(int* a, int begin, int end)
{
	int left = begin, right = end;
	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 begin, int end)
{
	if (begin >= end)
	{
		return;
	}

		int keyi = PartSort1(a, begin, end);
		//[begin,keyi-1] keyi[keyi+1,end]
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
}

int main()
{
    int a[] = { 6,1,2,7,9,3,4,5,8,10};
	QuickSort(a, 0, sizeof(a) / sizeof(int)-1);//sizeof(a) / sizeof(int) - 1 是为了获取数组 a 的元素个数,并将其减1作为快速排序的结束索引。
	
    return 0;
}

第一趟快排排好了key的位置,并且key将数组分为左右两个区间,接下来就和二叉树的递归差不多了。 

2.6.3 演示图

  1.  选取最左边的为key值,
  2. 右边先走,找到比key值小的值,停下;
  3. 左边走,找到比key值大的值,停下
  4. 交换left和right位置的数值,重复2,3,4过程,直到相遇
  5. 相遇时,相遇位置的值与key位置的值交换,重复上述过程

 【数据结构】——常见排序算法(演示图+代码+算法分析)_第7张图片

2.6.4算法分析 

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(logN)
  4. 稳定性:不稳定 

 此外,key值的位置尤为关键,如果面对的是一个有序的数据,key值仍然取的是最最左边的值的话,此时的时间复杂度会达到O(N^2).

【数据结构】——常见排序算法(演示图+代码+算法分析)_第8张图片

所以就有了,key值的三数取中 :

2.6.5三数取中

为什么要使用三数取中法呢?这是因为快速排序的性能高度依赖于关键值的选择。如果每次都选择最大或最小的元素作为关键值,会导致分区极不平衡,使得快速排序的效率大大降低,时间复杂度接近O(n^2)。而通过选择较为中间的元素作为关键值,可以有效避免最坏情况的发生,减少排序的比较和交换次数,提高快速排序的效率。

三数取中后,快排瞬间从最坏变成最好,快排几乎不会出现最坏的情况。快排的时间复杂度可以认为时O(N*logN)

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

2.6.6挖坑法

先将第一个数据存放在临时变量key中,形成一个坑位;右边找比key小的值,找到后把该位置的值赋值到坑位中,自身的位置形成新的坑位;左边找比key大的值,找到后把该位置的值赋值到坑位中,自身的位置新的坑位;当相遇时将key的值放到相遇的位置;重复上述过程。

代码

//挖坑法
int PartSort2(int* a, int begin, int end)
{
	int mid = GetMidIndex(a, begin, end);
	Swap(&a[begin], &a[mid]);

	int left = begin, right = end;
	int keyi = a[left];
	int hole = left;
	while (left < right)
	{
		while (left < right && a[right] >= keyi)
		{
			--right;
		}
		a[hole] = a[right];
		hole = right;
		while (left < right && a[left] <= keyi)
		{
			++left;
		}
		a[hole] = a[left];
		hole = left;
	}
	a[hole] = keyi;
	return hole;
}

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	if ((end - begin + 1) < 15)
	{
		//小区间用直接插入代替,减少递归调用次数
		InsertSort(a + begin, end-begin+1);
	}
	else
	{
		int keyi = PartSort2(a, begin, end);
		//[begin,keyi-1] keyi[keyi+1,end]
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
	}
}

int main()
{
    int a[] = { 6,1,2,7,9,3,4,5,8,10};
	QuickSort(a, 0, sizeof(a) / sizeof(int)-1);
    //打印
    return 0;
}

演示图

2.6.7前后指针法 

代码

int PartSort3(int *a,int begin,int end)
{
	int mid = GetMidIndex(a, begin, end);
	Swap(&a[begin], &a[mid]);

	int keyi = begin;
	int prev = begin, cur = begin + 1;
	while (cur <= end)
	{
		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 begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	if ((end - begin + 1) < 15)
	{
		//小区间用直接插入代替,减少递归调用次数
		InsertSort(a + begin, end-begin+1);
	}
	else
	{
		int keyi = PartSort3(a, begin, end);
		//[begin,keyi-1] keyi[keyi+1,end]
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
	}
}

int main()
{
    int a[] = { 6,1,2,7,9,3,4,5,8,10};
	QuickSort(a, 0, sizeof(a) / sizeof(int)-1);
    //打印
    return 0;
}

 演示图

1、cur找比key值小,找到后停下来

2、++prev,交换cur位置的值和prev位置的值

3、cur越界后,交换key位置的值和 prev位置的值

2.6.8快排的非递归实现

由于快速排序的递归实现每次递归都要开辟栈帧,当数据量大到一定程度可能会造成栈溢出,所以可以有快排的非递归实现。

可以用栈结构或者队列对数据进行存储和释放,此处用栈。

代码

//快排非递归实现
void QuickSortR(int* a, int begin, int end)
{
	ST st;
	StackInit(&st);
	StackPush(&st, begin);
	StackPush(&st, end);

	while (!StackEmpty(&st))
	{
		int right = StackTop(&st);
		StackPop(&st);
		int left = StackTop(&st);
		StackPop(&st);

		int keyi = PartSort3(a, left, right);
		if (keyi + 1 < right)                 //处理前面的数据时,后面的数据先进栈
		{
			StackPush(&st, keyi + 1);
			StackPush(&st, end);
		}
		if (left < keyi - 1)
		{
			StackPush(&st, left);
			StackPush(&st, keyi - 1);
		}
	}
	StackDestroy(&st);
}

int main()
{
    int a[] = { 6,1,2,7,9,3,4,5,8,10,8 };
	QuickSortR(a, 0, sizeof(a) / sizeof(int) - 1);
	//打印数组元素
    return 0;
}

实现的思想和递归的思想一样,用栈记录数组对应的下标:首先数组第一个元素下标和最后一个元素下标入栈,进入while循环,出栈,然后执行第一趟快排并返回keyi的下标;

此时key值把数组分为左边小和右边大的两个区间;

然后由于栈是先进后出的结构,我们先处理左区间的值,所以先入右区间的第一个数的下标(keyi+1)和右区间最后一个数的下标end,再入左区间的left和keyi-1,这样出栈的时候就可以先出左区间的值的下标了。

演示图
【数据结构】——常见排序算法(演示图+代码+算法分析)_第9张图片

 例如上图中,数组有10个值,数组第一个元素下标0和最后一个元素9先入栈,然后出栈,调用快速排序函数并返回keyi的下标;然后右区间第一个元素的下标6和最后一个元素下标9入栈,左区间第一个元素下标0和最后一个元素下标4入栈,然后出栈,对左区间进行快速排序......

 3.  完整代码:

 Sort.h

#pragma once
#include
#include
#include

void PrintArray(int* a, int n);//打印
void InsertSort(int* a, int n);//插入排序
void ShellSort(int* a, int n);
void HeapSort(int* a, int n);
void SelectSort(int* a, int n);
void BublbleSort(int* a, int n);
void QuickSort(int* a, int begin, int end);
void QuickSortNonR(int* a, int begin, int end);

Stack.h 

#pragma once
#include
#include
#include
#include

typedef int STDatetype;
typedef struct Stack
{
	STDatetype* a;
	int capacity;
	int top;                 //初始化为0,表示栈顶位置下一个位置的下标
}ST;

void StackInit(ST* ps);
void StackDestroy(ST* ps);
void StackPush(ST* ps,STDatetype x);
void StackPop(ST* ps);

STDatetype StackTop(ST* ps);

bool StackEmpty(ST* ps);
int StackSize(ST* ps);

Sort.c 

#define  _CRT_SECURE_NO_WARNINGS
#include"Sort.h"
#include"Stack.h"

void PrintArray(int* a, int n)
{
	for (int i = 0; i < n; ++i)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

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 1)
	{
		gap = gap / 3 + 1;

		for (int i = 0; i < n - gap; ++i)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}

}

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child a[child])
		{
			++child;
		}
		//孩子大于父亲,交换,继续向下调整
		//孩子小于父亲,则调整结束
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

//O(N*logN)
//在排序过程中,每次将堆顶元素与当前堆的最后一个元素进行交换后,再通过向下调整操作来维护堆的性质。
//这样,每次交换之后,最大的元素就被放置在了数组的末尾位置。随着排序的进行,每次交换都会将当前堆中的最大值移动到数组的末尾,
//所以最后每次的最后一个数是最大的。经过多次迭代,整个数组就会按照从小到大的顺序排序完成
void HeapSort(int* a, int n)
{
	//向下调整--O(N)
	//升序,建大堆
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}

	//O(N*logN)
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

//O(N^2)
//根插入排序比较,谁更好——插入
//插入适应性很强,对于有序,局部有序,都能效率提升

//任何情况下都是O(N^2)包括有序或者接近有序
void SelectSort(int* a, int n)
{
	int begin = 0, end = n-1;
	while (begin < end)
	{
		int mini = begin, 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]);
		if (maxi == begin)
		{
			maxi = mini;
		}
		Swap(&a[end], &a[maxi]);
		--end;
		++begin;
	}
}
//最大的数往下沉
void BubbleSort(int* a, int n)
{
	for (int j = 0; j < n; ++j)
	{
		int exchange = 0;
		for (int i = 1; i < n - j; ++i)
		{
			if (a[i - 1] > a[i])
			{
				Swap(&a[i - 1], &a[i]);
				exchange = 1;
			}
		}
		//一趟冒泡过程中,如果没有发生交换,说明已经有序
		if (exchange == 0)
		{
			break;
		}
	}

}

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

//Hoare
int PartSort1(int* a, int begin, int end)
{
	int mid = GetMidIndex(a, begin, end);
	Swap(&a[begin], &a[mid]);

	int left = begin, right = end;
	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;
}

//挖坑法
int PartSort2(int* a, int begin, int end)
{
	int mid = GetMidIndex(a, begin, end);
	Swap(&a[begin], &a[mid]);

	int left = begin, right = end;
	int keyi = a[left];
	int hole = left;
	while (left < right)
	{
		while (left < right && a[right] >= keyi)
		{
			--right;
		}
		a[hole] = a[right];
		hole = right;
		while (left < right && a[left] <= keyi)
		{
			++left;
		}
		a[hole] = a[left];
		hole = left;
	}
	a[hole] = keyi;
	return hole;
}

//双指针法
int PartSort3(int *a,int begin,int end)
{
	int mid = GetMidIndex(a, begin, end);
	Swap(&a[begin], &a[mid]);

	int keyi = begin;
	int prev = begin, cur = begin + 1;
	while (cur <= end)
	{
		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 begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	if ((end - begin + 1) < 15)
	{
		//小区间用直接插入代替,减少递归调用次数
		InsertSort(a + begin, end-begin+1);
	}
	else
	{
		int keyi = PartSort3(a, begin, end);
		//[begin,keyi-1] keyi[keyi+1,end]
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
	}
}

//快排非递归实现
void QuickSortR(int* a, int begin, int end)
{
	ST st;
	StackInit(&st);
	StackPush(&st, begin);
	StackPush(&st, end);

	while (!StackEmpty(&st))
	{
		int right = StackTop(&st);
		StackPop(&st);
		int left = StackTop(&st);
		StackPop(&st);

		int keyi = PartSort3(a, left, right);
		if (keyi + 1 < right)                 //处理前面的数据时,后面的数据先进栈
		{
			StackPush(&st, keyi + 1);
			StackPush(&st, end);
		}
		if (left < keyi - 1)
		{
			StackPush(&st, left);
			StackPush(&st, keyi - 1);
		}
	}
	StackDestroy(&st);
}

Stack.c 

#define  _CRT_SECURE_NO_WARNINGS
#include"Stack.h"


void StackInit(ST* ps)
{
	assert(ps);
	ps->a = (STDatetype*)malloc(sizeof(STDatetype) * 4);
	if (ps->a == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	ps->top = 0;
	ps->capacity = 4;
}


void StackDestroy(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 0;
}

void StackPush(ST* ps,STDatetype x)
{
	assert(ps);                  //防止是一个没有初始化的空栈
	if (ps->top == ps->capacity)
	{
		STDatetype* tmp = (STDatetype*)realloc(ps->a, ps->capacity * 2);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity *= 2;
	}
	ps->a[ps->top] = x;                //因为top初始化为0,所以先入栈再++;
	ps->top++;
}


bool StackEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

void StackPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	ps->top--;
}

int StackSize(ST* ps)
{
	assert(ps);
	return ps->top;
}

STDatetype StackTop(ST* ps)              //输出栈顶元素
{
	assert(ps);
	assert(!StackEmpty(ps));
	return ps->a[ps->top-1];
}

 test.c

#define  _CRT_SECURE_NO_WARNINGS
#include"Sort.h"

void TestInsertSort()
{
	int a[] = { 9,1,2,5,7,4,8,6,3,5 };
	InsertSort(a, sizeof(a) / sizeof(int));
	printf("InsertSort:\n");
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestShellSort()
{
	int a[] = { 9,8,7,6,5,4,3,2,1,5,3,1,0,4,-1,-2,-1 };
	ShellSort(a, sizeof(a) / sizeof(int));
	printf("ShellSort:\n");
	PrintArray(a, sizeof(a) / sizeof(int));
}


void TestHeapSort()
{
	int a[] = { 9,8,7,6,5,4,3,2,1,5,3,1,0,4,-1,-2,-1 };
	HeapSort(a, sizeof(a) / sizeof(int));
	printf("HeapSort:\n");
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestSelectSort()
{
	int a[] = { 9,1,2,5,7,4,8,6,3,5 };
	SelectSort(a, sizeof(a) / sizeof(int));
	printf("SelectSort\n");
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestBubbleSort()
{
	int a[] = { 9,1,2,5,7,4,8,6,3,5,-2,-1 };
	BubbleSort(a, sizeof(a) / sizeof(int));
	printf("BubbleSort\n");
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestQuickSort()
{
	int a[] = { 6,1,2,7,9,3,4,5,8,10,8 };
	//QuickSort(a, 0, sizeof(a) / sizeof(int)-1);//sizeof(a) / sizeof(int) - 1 是为了获取数组 a 的元素个数,并将其减1作为快速排序的结束索引。
	QuickSortR(a, 0, sizeof(a) / sizeof(int) - 1);
	printf("QuickSort:\n");
	PrintArray(a, sizeof(a) / sizeof(int));
}


int main()
{
	//TestInsertSort();
	//TestShellSort();
	//TestHeapSort();
	//TestSelectSort();
	//TestBubbleSort();
	TestInsertSort();
	TestShellSort();
	TestHeapSort();
	TestBubbleSort();
	TestSelectSort();
	TestQuickSort();
	return 0;
}

运行结果截图

【数据结构】——常见排序算法(演示图+代码+算法分析)_第10张图片

你可能感兴趣的:(数据结构,排序算法,算法,数据结构)