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

news2024/7/6 17:55:05

目录

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.2 稳定性

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

2.  常见排序算法的实现

2.1 插入排序

2.1.1基本思想

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

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<a[end])
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}

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

2.1.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演示图

  • 预排序时:
  • 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演示图

(小的数据往前放)

  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<n)
	{
		//确认child指向大的那个孩子
		if (child + 1 < n && a[child + 1] > 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演示图

 

 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位置的值交换,重复上述过程

 

2.6.4算法分析 

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

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

所以就有了,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,这样出栈的时候就可以先出左区间的值的下标了。

演示图

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

 3.  完整代码:

 Sort.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>

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<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.h>

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<a[end])
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}

//O(N^1.3)
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;
		}
	}

}

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<n)
	{
		//确认child指向大的那个孩子
		if (child + 1 < n && a[child + 1] > 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;
}

运行结果截图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/719759.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[Visual Studio 报错] error 找不到指定的 SDK“Microsoft

[Visual Studio 2022 报错] error : 找不到指定的 SDK“Microsoft.NET.Sdk.Web” 问题描述&#xff1a; 在新电脑上安装了VS2022&#xff0c;打开现有项目的解决方案后的时候报了这个错&#xff0c;所有projet文件都加载失败,如图所示&#xff1a; 报错分析及解决 打开项目配…

黑客是怎样炼成的?

前言 首先我谈下对黑客&网络安全的认知&#xff0c;其实最重要的是兴趣热爱&#xff0c;不同于网络安全工程师&#xff0c;他们大都是培训机构培训出来的&#xff0c;具备的基本都是防御和白帽子技能&#xff0c;他们绝大多数的人看的是工资&#xff0c;他们是为了就业而学…

MFC扩展库BCGControlBar Pro v33.5新版亮点 - 控件、脚本管理增强

BCGControlBar库拥有500多个经过全面设计、测试和充分记录的MFC扩展类。 我们的组件可以轻松地集成到您的应用程序中&#xff0c;并为您节省数百个开发和调试时间。 BCGControlBar专业版 v33.5已正式发布了&#xff0c;此版本包含了Ribbon&#xff08;功能区&#xff09;自定义…

CSS文本样式

CSS文本样式 1、字体 友情提醒&#xff1a; 字体有没有版权&#xff1f; 省略写法 语法&#xff1a; [ [ <‘font-style’> || || <‘font-weight’> || <‘font-stretch’> ]? <‘font-size’> [ / <‘line-height’> ]? <‘font-fam…

IDEA2023.1.3自带插件禁用,减少内存占用

前言 前两个星期安装了idea2023.1.3&#xff08;之前用的一直是idea2020.3版本&#xff09;&#xff0c;我发现新版界面确实更好看一些&#xff0c;而且启动速度也非常快&#xff0c;打开多个项目也一样很快&#xff0c;都是秒开。但是吧&#xff0c;它的内存占用比idea2020.3…

【若依】框架搭建,前端向后端如何发送请求,验证码的实现,开启注册功能

若依框架 若依框架&#xff08;Ruoyi&#xff09;是一款基于Spring Boot和Spring Cloud的开源快速开发平台。它提供了一系列的基础功能和通用组件&#xff0c;能够帮助开发者快速构建企业级应用。若依框架采用了模块化的设计理念&#xff0c;用户可以选择需要的功能模块进行集…

(03)QEMU模拟ATF启动

QEMU启动 准备一个目录qemu_boot存放所有镜像文件。最终启动需要的镜像如下所示。 Image QEMU_EFI.fd bl1.bin bl2.bin bl31.bin fip.bin flash.bin rootfs.cpio.gz准备镜像 EDK2 下载QEMU_EFI。 wget http://snapshots.linaro.org/components/kernel/leg-virt-tian…

go-redis

安装redis&#xff08;docker&#xff09; sudo docker pull redis sudo docker images 在官网下载redis.conf配置文件 redis官网&#xff1a;http://www.redis.cn/download.html 将下载后的压缩包解压得到redis.conf文件&#xff0c;放到自己的目录&#xff0c;我的是/hom…

数据结构与算法——图

&#x1f60a;数据结构与算法——图 &#x1f680;前言&#x1f680;图的基本概念&#x1f6a2;图的定义&#x1f6a2;图的基本操作&#x1f6a2;无向图和有向图&#x1f6a2;完全图&#x1f6a2;顶点的度、入度和出度&#x1f6a2;子图&#x1f6a2;顶点关系常用术语&#x1f…

我想在我自己的系统中加入微信支付功能,原来这么简单!!!

微信支付功能实现 一、创建SpringBoot项目 我们首先创建一个基本的SpringBoot项目。添加相关的依赖。 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>&…

php宝塔搭建EMLOG站长工具箱网站自适应PC手机端php源码

大家好啊&#xff0c;我是测评君&#xff0c;欢迎来到web测评。本期给大家带来一套站长工具箱网站自适应PC手机端php源码。感兴趣的朋友可以自行下载学习。 技术架构 PHP5.6 nginx mysql5.6 JS CSS HTMLcnetos7以上 宝塔面板 文字搭建教程 下载源码&#xff0c;宝塔添…

SciencePub学术 | 数据处理类重点SCIEEI征稿中

SciencePub学术 刊源推荐: 数据处理类重点SCI&EI征稿中&#xff01;信息如下&#xff0c;录满为止&#xff1a; 一、期刊概况&#xff1a; 数据处理类重点SCIE&EI 【期刊简介】IF&#xff1a;3.5-4.0&#xff0c;JCR2区&#xff0c;中科院4区&#xff1b; 【出版社】…

CSS选择器常见用法

目录 一.总体分类 二.基础选择器 &#xff08;1&#xff09;标签选择器 &#xff08;2&#xff09;类选择器 &#xff08;3&#xff09;id选择器 &#xff08;4&#xff09;通配符选择器&#xff08;特殊&#xff09; 三.复合选择器 &#xff08;1&#xff09;后代选择器 &…

注意看!!!Linux中Ubuntu22.04之SVN的三种配置方式【详解~】

注意看&#xff01;&#xff01;&#xff01;Linux中Ubuntu22.04之SVN的三种配置方式【详解&#xff5e;】 配置目录大全<1>I、 安装&#xff08;易上手&#x1f446;&#xff09;II、使用SVN&#xff08;简单易操作&#xff09;III、配置SVN&#xff08;精通&#xff09…

一个事务回滚问题的解决

同事遇见一个事务回滚的问题&#xff0c;从controller提交的时候出现&#xff0c;transaction marked as rollbackonly 这个错误。 从调用栈上来看&#xff0c;没啥可用的信息&#xff0c;另外基本没有啥用户代码&#xff0c;都是框架JPA的代码 这个事其实以前遇见过类似的&…

Spring Boot 中的健康检查是什么, 如何使用

Spring Boot 是一个非常流行的 Java Web 开发框架&#xff0c;它提供了许多方便的功能&#xff0c;其中之一就是健康检查。健康检查是一种用来确保应用程序和其所依赖的服务的状态正常的机制。在本文中&#xff0c;我们将探讨 Spring Boot 中的健康检查是什么&#xff0c;以及如…

货损、灭失、延迟配送怎么办?打破这几个点才是关键

物流行业离不开讨论的一个核心话题就是物流质损。不管是在货物的运输、存储还是配送环节&#xff0c;都有可能发生货物损坏的情况。 要降低货物质损的风险&#xff0c;有两个基本因素需要考虑&#xff1a;距离长短和环节次数。距离越短&#xff0c;货物摇晃挤压影响就越少&…

odoo 集成 Minio

将Odoo与MinIO集成的优点包括以下几点&#xff1a;1、可扩展性&#xff1a;MinIO是一个高性能、可扩展的对象存储服务&#xff0c;可以 轻松处理大规模的数据存储和访问需求。通过将Odoo与MinIO集成&#xff0c;您可以有效地扩展和管理您的数据存储。2、弹性存储&#xff1a;Mi…

【数据结构课程设计系列】图书管理系统操作演示

图书管理系统操作演示 随着计算机技术的进步和人们对系统需求的进一步提高&#xff0c;学校对于图书馆信息管理也相应的提升了。学校对于图书馆信息管理主要侧重于数据的更新快捷、准确、占用较少的人力资源&#xff0c;而达到最大的办公效率。 本次设计所解决的主要问题就是如…