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

这篇具有很好参考价值的文章主要介绍了【数据结构】——常见排序算法(演示图+代码+算法分析)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

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;
}

运行结果截图

【数据结构】——常见排序算法(演示图+代码+算法分析)文章来源地址https://www.toymoban.com/news/detail-514227.html

到了这里,关于【数据结构】——常见排序算法(演示图+代码+算法分析)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构】 常见的八大排序算法

    排序有 内部排序和外部排序 ,内部排序是数据记录在内存中进行排序,这里八大排序就是内部排序,指直接插入,希尔,选择,堆排,冒泡,快排,归并,计数。 下面让我们来共同学习这八大排序吧!🤗🤗🤗 什么是外部排序: 外排序是数据量较大,内存放不下,数据放到外

    2024年02月12日
    浏览(58)
  • 【数据结构】常见排序算法——常见排序介绍、选择排序(直接选择排序、堆排序)交换排序(冒泡排序)

      选择排序是一种简单但不高效的排序算法,其基本思想是从待排序的数据中选择最小(或最大)的元素放到已排序的数据末尾。具体操作步骤如下: (1)找到数据中最小的元素,并把它交换到第一个位置; (2)在剩下未排序的元素中找到最小的元素,并把它交换到已排

    2024年02月04日
    浏览(38)
  • 【数据结构---排序】庖丁解牛式剖析常见的排序算法

    排序在我们生活中处处可见,所谓排序,就是使一串记录,按照其中的某个或某些的大小,递增或递减的排列起来的操作。 常见的排序算法可以分为四大类:插入排序,选择排序,交换排序,归并排序;其中,插入排序分为直接插入排序和希尔排序;选择排序分为直接

    2024年02月16日
    浏览(47)
  • 【一起学数据结构与算法】几种常见的排序(插入排序、选择排序、交换排序、归并排序)

    排序是计算机内经常进行的一种操作,其目的是将一组 “无序” 的记录序列调整为 “有序” 的记录序列。分 内部排序 和 外部排序 ,若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序。反之,若参加排序的记录数量很大,整个序列的排序过程不可能

    2023年04月09日
    浏览(30)
  • Java常见算法_常见的查找算法和排序算法——简介及代码演示

            在本文中我将介绍Java中的常见算法,查找算法包括基本查找、二分查找、插值查找和分块查找。排序算法包括冒泡排序、选择排序、插入排序和快速排序 1.基本查找: 代码: 这是简单的基本查找,通过遍历数组来查看元素是否存在 运行结果: 基本查找小练习: 代

    2024年04月10日
    浏览(30)
  • 数据结构(C语言实现)——常见排序算法的基本思想及实现(快速排序的三种方法和优化及非递归实现快速排序)

    生活中几乎处处都会用到排序,比如:网购时的店铺顺序,学生成绩的排名等,今天我们就来学习数据结构中常见的几种排序算法。 排序 :所谓排序,就是使一串记录,按照其中的某个或某些的大小,递增或递减的排列起来的操作。 稳定性 :假定在待排序的记录序列

    2023年04月24日
    浏览(40)
  • 数据结构:手撕图解七大排序(含动图演示)

    插入排序分为直接插入排序和希尔排序,其中希尔排序是很值得学习的算法 希尔排序的基础是直接插入排序,先学习直接插入排序 直接插入排序类似于打扑克牌前的整牌的过程,假设我们现在有2 4 5 3四张牌,那么应该怎么整牌? 方法很简单,把3插到2和4中间,这样就完成了

    2024年02月15日
    浏览(31)
  • 【算法与数据结构】归并排序的代码实现(详细图解)以及master公式的讲解

    目录 1、归并排序  1.1、算法描述  1.2、图解说明 2、代码实现  3、master公式 3.1、公式以及结论 3.2、适用于某些特殊的递归 3.3、计算归并排序的时间复杂度 归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用 递归 或者说是 分治法 (Divide and Conquer)的一个非

    2024年02月08日
    浏览(45)
  • 数据结构与算法——希尔排序(引例、希尔增量序列、原始希尔排序、代码、时间复杂度、Hibbard增量序列、Sedgewick增量序列)

    目录 引例 希尔增量序列 原始希尔排序 代码(C语言) 时间复杂度 更多增量序列 Hibbard增量序列 Sedgewick增量序列 希尔排序(by Donald Shell) 给以下序列进行排序:  先以5的间隔进行插入排序:  再以3的间隔进行插入排序: 最后再以1为间隔做插入排序,即常规插入排序 ,得

    2024年02月16日
    浏览(32)
  • 【数据结构】七种常见的排序

    目录 1、排序的概念即运用 1.1、排序的概念  1.2、常见排序算法的分类 2、插入排序 2.1、排序原理 2.2、直接插入排序  2.3、希尔排序(缩小增量排序) 3、选择排序 3.1、直接选择排序  3.2、堆排序   4、选择排序 4.1、冒泡排序  4.2、快速排序  4.2.1、挖坑法实现快速排序 4

    2024年02月04日
    浏览(25)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包