❤️ 「七大排序算法」

目录

前言

七大排序预览

冒泡排序

选择排序

插入排序

希尔排序

归并排序

堆排序

快速排序


前言

关于冒泡排序选择排序、归并排序算法的原理说明为转载,他们的描述与介绍图文并茂,比本人重新讲述要更好,于是转载了介绍比较好的部分内容,并已经注明原内容地址,其算法实现则统一改用C语言实现,更简单明了。希望本篇博客能让你一次性吃透七大排序算法,原创不易,望读者点赞支持!

扫描二维码关注公众号,回复: 13569966 查看本文章

七大排序预览

排序算法
平均时间复杂度
最好情况
最坏情况
排序方式
稳定性
冒泡排序 
O(n * n)
O(n)
O(n * n)
In-place
稳定
选择排序
O(n * n)
O(n * n)
O(n * n)
In-place
In-place
插入排序
O(n * n)
O(n)
O(n * n)
In-place
稳定
希尔排序
O(n * log n)
O(n * log n)
O(n * log n)
In-place
不稳定
归并排序
O(n * log n)
O(n * log n)
O(n * log n)
Out-place
稳定
堆排序
O(n * log n)
O(n * log n)
O(n * log n)
In-place
不稳定
快速排序
O(n * log n)
O(n * log n)
O(n * n)
In-place
不稳定

冒泡排序

此块内容转载自博客园,原代码为java描述,以下本人使用C语言描述。原文地址:三分钟彻底理解冒泡排序

冒泡排序:如果遇到相等的值不进行交换,那这种排序方式是稳定的排序方式。

1.原理:比较两个相邻的元素,将值大的元素交换到右边

2.思路:依次比较相邻的两个数,将比较小的数放在前面,比较大的数放在后面。

    (1)第一次比较:首先比较第一和第二个数,将小数放在前面,将大数放在后面。

    (2)比较第2和第3个数,将小数 放在前面,大数放在后面。

    ......

    (3)如此继续,知道比较到最后的两个数,将小数放在前面,大数放在后面,重复步骤,直至全部排序完成

    (4)在上面一趟比较完成后,最后一个数一定是数组中最大的一个数,所以在比较第二趟的时候,最后一个数是不参加比较的。

    (5)在第二趟比较完成后,倒数第二个数也一定是数组中倒数第二大数,所以在第三趟的比较中,最后两个数是不参与比较的。

    (6)依次类推,每一趟比较次数减少依次

3.举例

    (1)要排序数组:[10,1,35,61,89,36,55]

    (2)第一趟排序:

      第一次排序:10和1比较,10大于1,交换位置       [1,10,35,61,89,36,55]

      第二趟排序:10和35比较,10小于35,不交换位置  [1,10,35,61,89,36,55]

      第三趟排序:35和61比较,35小于61,不交换位置  [1,10,35,61,89,36,55]

      第四趟排序:61和89比较,61小于89,不交换位置  [1,10,35,61,89,36,55]

        第五趟排序:89和36比较,89大于36,交换位置   [1,10,35,61,36,89,55]

      第六趟排序:89和55比较,89大于55,交换位置   [1,10,35,61,36,55,89]

      第一趟总共进行了六次比较,排序结果:[1,10,35,61,36,55,89]

    

    (3)第二趟排序:

      第一次排序:1和10比较,1小于10,不交换位置  1,10,35,61,36,55,89

      第二次排序:10和35比较,10小于35,不交换位置    1,10,35,61,36,55,89

      第三次排序:35和61比较,35小于61,不交换位置     1,10,35,61,36,55,89

      第四次排序:61和36比较,61大于36,交换位置   1,10,35,36,61,55,89

      第五次排序:61和55比较,61大于55,交换位置   1,10,35,36,55,61,89

      第二趟总共进行了5次比较,排序结果:1,10,35,36,55,61,89

    (4)第三趟排序:

      1和10比较,1小于10,不交换位置  1,10,35,36,55,61,89

      第二次排序:10和35比较,10小于35,不交换位置    1,10,35,36,55,61,89

      第三次排序:35和36比较,35小于36,不交换位置     1,10,35,36,55,61,89

      第四次排序:36和61比较,36小于61,不交换位置   1,10,35,36,55,61,89

      第三趟总共进行了4次比较,排序结果:1,10,35,36,55,61,89

      到目前位置已经为有序的情形了。

4.算法分析

    (1)由此可见:N个数字要排序完成,总共进行N-1趟排序,每i趟的排序次数为(N-i)次,所以可以用双重循环语句,外层控制循环多少趟,内层控制每一趟的循环次数

    (2)冒泡排序的优点:每进行一趟排序,就会少比较一次,因为每进行一趟排序都会找出一个较大值。如上例:第一趟比较之后,排在最后的一个数一定是最大的一个数,第二趟排序的时候,只需要比较除了最后一个数以外的其他的数,同样也能找出一个最大的数排在参与第二趟比较的数后面,第三趟比较的时候,只需要比较除了最后两个数以外的其他的数,以此类推……也就是说,没进行一趟比较,每一趟少比较一次,一定程度上减少了算法的量。

    (3)时间复杂度

    1.如果我们的数据正序,只需要走一趟即可完成排序。所需的比较次数C和记录移动次数M均达到最小值,即:Cmin=n-1;Mmin=0;所以,冒泡排序最好的时间复杂度为O(n)。

    2.如果很不幸我们的数据是反序的,则需要进行n-1趟排序。每趟排序要进行n-i次比较(1≤i≤n-1),且每次比较都必须移动记录三次来达到交换记录位置。在这种情况下,比较和移动次数均达到最大值:

    

  综上所述:冒泡排序总的平均时间复杂度为:O(n2) ,时间复杂度和数据状况无关。

5.C语言代码实现

#include <stdio.h>

void BubbleSort(int arr[],int len)
{
	if (len < 1 || arr==nullptr) return;

	for(int i=0;i<len;i++)
		for (int j=0; j < len-i-1; j++)
		{
			if (arr[j]> arr[j + 1])
			{
				int temp = arr[j + 1];
				arr[j + 1] = arr[j];
				arr[j] = temp;
			}
		}
}

void PrintAddr(int arr[], int len)
{
	for (int i = 0; i < len; i++)
		printf("%d ", arr[i]);
	printf("\n");
}

int main()
{
	int arr[] = { 10,1,35,61,89,36,55};
	int len = sizeof(arr) / sizeof(arr[0]);
	BubbleSort(arr, len);
	PrintAddr(arr, len);
}

选择排序

此块内容转载自博客园,原代码为java描述,以下本人使用C语言描述。原文地址:简单选择排序

简单选择排序是一种选择排序

选择排序:每趟从待排序的记录中选出关键字最小的记录,顺序放在已排序的记录序列末尾,直到全部排序结束为止。

简单排序处理流程

(1)从待排序序列中,找到关键字最小的元素;

(2)如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换;

(3)从余下的 N - 1 个元素中,找出关键字最小的元素,重复(1)、(2)步,直到排序结束。

如图所示,每趟排序中,将当前 i 小的元素放在位置 i 上。 

时间复杂度

简单选择排序的比较次数与序列的初始排序无关。 假设待排序的序列有 N 个元素,则比较次数总是N (N - 1) / 2

而移动次数与序列的初始排序有关。当序列正序时,移动次数最少,为 0.

当序列反序时,移动次数最多,为3N (N - 1) /  2。

所以,综合以上,简单排序的时间复杂度为 O(N2)。 

 C语言代码实现


#include <stdio.h>
void SelectSort(int arr[], int len)
{
	if (len < 1 || arr == nullptr) return;

	for (int i = 0; i < len; i++)
	{
		int index = i;//用来保存最小值的索引
		for (int j = i + 1; j < len; j++)
		{
			if (arr[j] < arr[index])
			{
				index = j;
			}
		}
		int temp = arr[i];
		arr[i] = arr[index];
		arr[index] = temp;
	}
}
void PrintAddr(int arr[], int len)
{
	for (int i = 0; i < len; i++)
		printf("%d ", arr[i]);
	printf("\n");
}
int main()
{
	int arr[] = { 9,1,2,5,7,4,8,6,3,5 };
	int len = sizeof(arr) / sizeof(arr[0]);
	SelectSort(arr, len);
	PrintAddr(arr, len);
}

插入排序

插入排序: 它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用 in-place 排序(即只需用到 O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
具体算法描述如下
1. 从第一个元素开始,该元素可以认为已经被排序;
2. 取出下一个元素,在已经排序的元素序列中从后向前扫描;
3. 如果该元素(已排序)大于新元素,将该元素移到下一位置;
重复步骤 3,直到找到已排序的元素小于或者等于新元素的位置;
4. 将新元素插入到该位置;重复步骤 2~5。
举例:
171 161 163 165 167 169
1. 首先, 我们只考虑第一个元素,从第一个元素 171 开始,该元素可以认为已经被排序;
171 161 163 165 167 169
2. 取下一个元素 161 并记录,并让 161 所在位置空出来,在已经排序的元素序列中从后向前扫描;
171 163 65 167 169
3. 该元素(171)大于新元素,将该元素移到下一位置;
171 163 165 167 169
4. 171 前已经没有最大的元素了, 则将 161 插入到空出的位置
161 171 163 165 167 169
5. 取下一个元素 163,并让 163 所在位置空出来,在已经排序的元素序列中从后向前扫描;
161 171 165 167 169
6. 该元素(171)大于新元素 163,将该元素移到下一位置
161 171 165 167 169
7. 继续取 171 前的元素新元素比较, 直到找到已排序的元素小于或者等于新元素的位置;新
元素大于 161,则直接插入空位中
161 163 171 165 167 169
8. 重复步骤 2~7,直到完成排序
161 163 165 167 169  171
C语言代码实现
#include <stdio.h>

void InsertSort(int arr[], int len)
{
	for (int i = 1; i < len; i++)
	{
		int curVaule = arr[i];
		int preIndex = i - 1;
		
		while (preIndex >= 0 && arr[preIndex] > curVaule)
		{
			arr[preIndex + 1] = arr[preIndex];
			preIndex--;
		}
		arr[preIndex + 1] = curVaule;
	}
}

void PrintAddr(int arr[], int len)
{
	for (int i = 0; i < len; i++)
		printf("%d ", arr[i]);
	printf("\n");
}
int main()
{
	int arr[] = { 171,161,163,165,167,169 };
	int len = sizeof(arr) / sizeof(arr[0]);
	InsertSort(arr, len);
	PrintAddr(arr, len);
}
	

希尔排序

希尔排序是希尔(Donald Shell)于 1959 年提出的一种排序算法。
希尔排序也是 一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为 缩小增量排序。它与插入排序的不同之处在于,它会优先比较距离较远的元素
希尔排序的基本步骤
选择增量 : gap=length/2,缩小增量: gap = gap/2
增量序列:用序列表示增量选择, {n/2, (n/2)/2, …, 1}
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
选择一个增量序列 t1,t2,…,tk,其中 ti>tj,tk=1;
按增量序列个数 k,对序列进行 k 趟排序;
每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进
行直接插入排序;
仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
举例:

C语言代码实现

#include<stdio.h>
void ShellSort(int arr[], int len)
{
	
	for (int gap = len / 2; gap > 0; gap /= 2)
	{
		for (int i = gap; i<len; i++)
		{
			int curValue = arr[i];
			int preIndex = i - gap;

			while (preIndex >= 0 && arr[preIndex] > curValue)
			{
				arr[preIndex + gap] = arr[preIndex];
				preIndex -= gap;
			}
			arr[preIndex + gap] = curValue;
		}
	}
}
void PrintAddr(int arr[], int len)
{
	for (int i = 0; i < len; i++)
		printf("%d ", arr[i]);
	printf("\n");
}
int main()
{
	int arr[] = { 8,9,1,7,2,3,5,4,6,0 };
	int len = sizeof(arr) / sizeof(arr[0]);
	ShellSort(arr, len);
	PrintAddr(arr, len);
}

归并排序

此块内容转载自博客园,原代码为java描述,以下本人使用C语言描述。原文地址:图解排序

 归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。

(如果对于分治算法思想理解较难,可移步至本人博客:❤️ 「呕心之作」 一篇博客带你精通「 五大核心算法」❤️

分而治之

 可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。

合并相邻有序子序列

 再来看看阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤。

C语言代码实现

#include <stdio.h>
#include<string.h>
void MergeAdd(int arr[], int left, int mid, int right, int *temp)
{
	int lmin = left;
	int rmin = mid;
	int index = left;
	
	while (lmin<mid && rmin<=right)
	{
		if (arr[lmin] < arr[rmin])
		{
			temp[index++] = arr[lmin++];	
		}
		else
		{
			temp[index++] = arr[rmin++];
		}
	}

	while (lmin < mid)
		temp[index++] = arr[lmin++];

	while (rmin <= right)
		temp[index++] = arr[rmin++];

	memcpy(arr + left, temp + left, sizeof(int)*(right - left + 1));
}

void MergeSort(int arr[], int left, int right, int *temp)
{
	if (left <0 || arr == nullptr) return;

	if (left < right)
	{
		int mid = (right+left)/2;
		MergeSort(arr, left, mid, temp);
		MergeSort(arr, mid+1, right, temp);
		MergeAdd(arr, left, mid + 1, right, temp);
	}
}
void PrintAddr(int arr[], int len)
{
	for (int i = 0; i < len; i++)
		printf("%d ", arr[i]);
	printf("\n");
}
int main()
{
		int arr[] = { 8,9,1,7,2,3,5,4,6,0 };
		int len = sizeof(arr) / sizeof(arr[0]);

		int *temp = new int[len];
		MergeSort(arr,0,len-1,temp);
		PrintAddr(arr, len);
		delete temp;
}

堆排序

关于堆的数据结构及算法实现详细介绍,请移步至本人博客数据结构之堆的定义、属性以及算法实现,这里仅对堆排序算法做介绍

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素.
(选择排序工作原理 - 第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零)
举例

数据结构之堆的定义、属性以及算法实现博客介绍的前提下,堆排序C语言实现代码如下:

void HeapSort(int *_arr,int size)
{
	int capacity = DEFAULT_CAPACITY > size ? DEFAULT_CAPACITY : size;
	Heap heap;
	heap.arr = _arr;
	heap.size = 0;

	if(size>0)
	{
		heap.size = size;
		buildHeap(heap);
	}

	while (heap.size > 0)
	{
		int tmp = heap.arr[0];
		heap.arr[0] = heap.arr[heap.size-1];
		heap.arr[heap.size - 1] = tmp;
		heap.size--;
		adjustDown(heap, 0);
	}
}

快速排序

快速排序原理
1. 每次选取第一个数为基准数;
2. 然后将大于和小于基准的元素分别放置于基准数两边-->"乾坤大挪移";
3. 继续分别对基准数两侧未排序的数据使用分治法进行细分处理,直至整个序列有序。
举例
C语言代码实现
 
#include <stdio.h>
int partition(int arr[], int low, int high)
{
	int i = low;
	int j = high;
	int base = arr[low];

	if (low < high)
	{
		while (i < j)
		{
			while (i < j && arr[j] >= base)
			{
				j--;
			}
			if (i < j)//右边有比基数小的数
			{
				arr[i++] = arr[j];
			}

			while (i < j&&arr[i] < base)
			{
				i++;
			}
			if (i < j)//左边有比基数大的数
			{
				arr[j--] = arr[i];
			}
		}

		arr[i] = base;
	}
	return i;
}
//快速排序
void QuickSort(int arr[], int low, int high)
{
	if (low < high)
	{
		int index = partition(arr, low, high);
		QuickSort(arr, low, index - 1);
		QuickSort(arr, index + 1, high);
	}
}

void PrintAddr(int arr[], int len)
{
	for (int i = 0; i < len; i++)
		printf("%d ", arr[i]);
	printf("\n");
}
int main()
{
	int arr[] = { 163, 161, 158, 165, 171, 170, 163, 159, 162 };

	int len = sizeof(arr) / sizeof(arr[0]);
	QuickSort(arr, 0, len - 1);
	PrintAddr(arr, len);
}

总结不易、一键三连!

猜你喜欢

转载自blog.csdn.net/weixin_40582034/article/details/119555338