面试必会八大排序算法(Python)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试必会八大排序算法(Python)相关的知识,希望对你有一定的参考价值。

参考技术A 一、插入排序

介绍

插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据。

算法适用于少量数据的排序,时间复杂度为O(n^2)。

插入排算法是稳定的排序方法。

步骤

①从第一个元素开始,该元素可以认为已经被排序

②取出下一个元素,在已经排序的元素序列中从后向前扫描

③如果该元素(已排序)大于新元素,将该元素移到下一位置

④重复步骤3,直到找到已排序的元素小于或者等于新元素的位置

⑤将新元素插入到该位置中

⑥重复步骤2

排序演示

算法实现

二、冒泡排序

介绍

冒泡排序(Bubble Sort)是一种简单的排序算法,时间复杂度为O(n^2)。

它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。

这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

原理

循环遍历列表,每次循环找出循环最大的元素排在后面;

需要使用嵌套循环实现:外层循环控制总循环次数,内层循环负责每轮的循环比较。

步骤

①比较相邻的元素。如果第一个比第二个大,就交换他们两个。

②对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。

③针对所有的元素重复以上的步骤,除了最后一个。

④持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

算法实现:

三、快速排序

介绍

快速排序(Quicksort)是对冒泡排序的一种改进,借用了分治的思想,由C. A. R. Hoare在1962年提出。

基本思想

快速排序的基本思想是:挖坑填数 + 分治法。

首先选出一个轴值(pivot,也有叫基准的),通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

实现步骤

①从数列中挑出一个元素,称为 “基准”(pivot);

②重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边);

③对所有两个小数列重复第二步,直至各区间只有一个数。

排序演示

算法实现

四、希尔排序

介绍

希尔排序(Shell Sort)是插入排序的一种,也是缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法,时间复杂度为:O(1.3n)。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

·插入排序在对几乎已经排好序的数据操作时, 效率高, 即可以达到线性排序的效率;

·但插入排序一般来说是低效的, 因为插入排序每次只能将数据移动一位。

基本思想

①希尔排序是把记录按下标的一定量分组,对每组使用直接插入算法排序;

②随着增量逐渐减少,每组包1含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法被终止。

排序演示

算法实现

五、选择排序

介绍

选择排序(Selection sort)是一种简单直观的排序算法,时间复杂度为Ο(n2)。

基本思想

选择排序的基本思想:比较 + 交换。

第一趟,在待排序记录r1 ~ r[n]中选出最小的记录,将它与r1交换;

第二趟,在待排序记录r2 ~ r[n]中选出最小的记录,将它与r2交换;

以此类推,第 i 趟,在待排序记录ri ~ r[n]中选出最小的记录,将它与r[i]交换,使有序序列不断增长直到全部排序完毕。

排序演示

选择排序的示例动画。红色表示当前最小值,黄色表示已排序序列,蓝色表示当前位置。

算法实现

六、堆排序

介绍

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。

利用数组的特点快速指定索引的元素。

基本思想

堆分为大根堆和小根堆,是完全二叉树。

大根堆的要求是每个节点的值不大于其父节点的值,即A[PARENT[i]] >=A[i]。

在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶。

排序演示

算法实现

七、归并排序

介绍

归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

基本思想

归并排序算法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

算法思想

自上而下递归法(假如序列共有n个元素)

① 将序列每相邻两个数字进行归并操作,形成 floor(n/2)个序列,排序后每个序列包含两个元素;

② 将上述序列再次归并,形成 floor(n/4)个序列,每个序列包含四个元素;

③ 重复步骤②,直到所有元素排序完毕。

自下而上迭代法

① 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;

② 设定两个指针,最初位置分别为两个已经排序序列的起始位置;

③ 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;

④ 重复步骤③直到某一指针达到序列尾;

⑤ 将另一序列剩下的所有元素直接复制到合并序列尾。

排序演示

算法实现

八、基数排序

介绍

基数排序(Radix Sort)属于“分配式排序”,又称为“桶子法”。

基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r)m) ,其中 r 为采取的基数,而m为堆数。

在某些时候,基数排序法的效率高于其他的稳定性排序法。

基本思想

将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。

基数排序按照优先从高位或低位来排序有两种实现方案:

MSD(Most significant digital) 从最左侧高位开始进行排序。先按k1排序分组, 同一组中记录, 关键码k1相等,再对各组按k2排序分成子组, 之后, 对后面的关键码继续这样的排序分组, 直到按最次位关键码kd对各子组排序后. 再将各组连接起来,便得到一个有序序列。MSD方式适用于位数多的序列。

LSD (Least significant digital)从最右侧低位开始进行排序。先从kd开始排序,再对kd-1进行排序,依次重复,直到对k1排序后便得到一个有序序列。LSD方式适用于位数少的序列。

排序效果

算法实现

九、总结

各种排序的稳定性、时间复杂度、空间复杂度的总结:

平方阶O(n²)排序:各类简单排序:直接插入、直接选择和冒泡排序;

从时间复杂度来说:

线性对数阶O(nlog₂n)排序:快速排序、堆排序和归并排序;

O(n1+§))排序,§是介于0和1之间的常数:希尔排序 ;

线性阶O(n)排序:基数排序,此外还有桶、箱排序。

八大排序总结---- 数据结构 (图解法) 面试必会! ! !

目录:

一.插入排序 (InsertSort)

对于插入排序的本质就是从后向前将所有的元素找到对应的位置进行插入即可.

void insertSort(int* arr, int n){
	
	//假设第一个数据是有序的
	//未插入数据[1,n)
	for (int i = 0; i < n; ++i){
		
		//从有序的最后一个位置进行遍历
		int end = i - 1;
		int data = arr[i];
		while (end >= 0 && arr[end] >= data){	//满足条件时
			
			//较大的数据向后移动
			arr[end + 1] = arr[end];		//数据后移
			--end;		//循环
		}
		arr[end + 1] = data;	//直接插入
	}
}

二.希尔排序 (ShellSort)

void shellSort(int* arr, int n){
	
	int gap = n;		//创建gap值
	//一趟哈希排序
	while (gap > 1){

		gap = gap / 3 + 1;	//让其等于一个值进行交换
		for (int i = gap; i < n; ++i){	//循环内部

			//同组数据,最后一个有序数组的位置
			int end = i - gap;
			//待插入的数据
			int data = arr[i];
			while (end >= 0 && arr[end]>data){

				arr[end + gap] = arr[end];	
				end -= gap;		//gap逐渐减到1
			}
			arr[end + gap] = data;		//交换
		}
	}
}

三.选择排序 (SelectSort)

选择排序从前向后遍历,找后面小于这个数(最小),然后将两者进行交换,依次向后移一位.

void selectSort(int* arr, int n){
	
	int start = 0;
	int end = n - 1;		//定义尾部节点
	while (start < end){		//循环开始
		
		//首先找到最小值的位置
		int minIdx = start;		
		int i;
		for (i = start + 1; i <= end; ++i){		//进行遍历
			
			if (arr[i] < arr[minIdx])		//如果存在最小的
				minIdx=i;	//让它等于i
		}
		//将最小值存在起始位置
		Swap(arr, start, minIdx);		//这里调用了一个最最最简单的交换元素,我就不具体写了,相信大家也会.
		//循环
		++start;
	}
}

四.堆排序 (HeapSort)

void shiftDown(int* arr, int n, int parent){
	
	int child = 2 * parent + 1;		//最后一个孩子节点的位置
	while (child < n){			//循环
		
		if (child + 1 < n&&arr[child + 1] > arr[child])	//存在则继续
			++child;
		if (arr[child]>arr[parent]){	//孩子节点大于父节点,则交换

			Swap(arr, child, parent);	//交换,如同图中的橙色线
			
			parent = child;		//更新
			child = 2 * parent + 1;	//更新孩子节点
		}
		else
			break;		//不满足直接结束
	}
}
void heapSort(int* arr, int n){
	
	for (int i = (n - 2) / 2; i >= 0; --i){
		
		shiftDown(arr, n, i);		//进行循环调用
	}
	int end = n - 1;		//最后的节点依次减1
	while (end > 0){		//end>0继续循环
		
		Swap(arr, end, 0);		//交换函数
		shiftDown(arr, end, 0);	//交换最后一个
		--end;		//直到end=0,循环结束
	}
}

五.冒泡排序 (BubbleSort)

void bubbleSort(int* arr,int n){
	
	//相邻元素进行比较
	//遍历范围:0-为排序的最后一位
	int end = n;
	while (end > 1){
		
		//第一次冒泡排序
		for (int i = 1; i < end; ++i){
			
			if (arr[i - 1]>arr[i]){		//这里的i和i-1就相当于i和j
				
				//大的元素向后移动
				Swap(arr, i - 1, i);		//简单交换函数,不描写接口
			}
		}
		--end;		//主要进行循环
	}
}

六.快速排序 (QuickSort)

1.hoare法

//获取基准值
//下面输出的都会基准值
//这段代码主要是理解,其结构不难,不进行过多的注释
int getMid(int* arr, int begin, int end){

	int mid = begin + (end - begin) / 2;	

	if (arr[begin] > arr[mid]){			//==========1.开始大于中间

		if (arr[mid] > arr[end])	
			return mid;
		else if (arr[begin] > arr[end])	
			return end;
		else
			return begin;		
	}
	else{								//=========2.开始小于等于中间

		if (arr[mid] < arr[end])		
			return mid;
		else if (arr[begin] < arr[end])
			return end;
		else
			return begin;
	}
}

//划分函数    实现数组的划分,进行分别遍历的操作
int partion(int* arr, int begin, int end){

	//获取基准值位置
	int mid = getMid(arr, begin, end);
	//把基准值放到起始位置
	Swap(arr, begin, end);

	//首先选择基准值
	int key = arr[begin];
	int start = begin;

	while (begin < end){		//后置比较大的时候

		//1.==============================从后向前找小于基准值的位置
		while (begin < end&&arr[end] >= key)
			--end;

		//2.===============================从前向后找大于的位置
		while (begin < end&&arr[begin] <= key)
			++begin;
		//3.===============================用函数交换
		Swap(arr, begin, end);
	}
	//交换基准值与相遇位置的数据
	Swap(arr, start, begin);
	return begin;
}

//快速排序
void quickSort(int* arr, int begin, int end){
	
	if (begin >= end)
		return;
		
	//div:一次划分后基准值
	int div = partion(arr, begin, end);
	//分别进行左右两边的快排
	//[begin,div-1]
	//[div+1,end]
	quickSort(arr, begin, div - 1);
	quickSort(arr, div + 1, end);
}

2.挖坑法

//获取中间值的函数接口
int getMid(int* arr, int begin, int end){

	int mid = begin + (end - begin) / 2;	//获取中间值

	if (arr[begin] > arr[mid]){		//对所处的位置进行比较,最终获取中间的值
									//很简单的,我就不具体解释了,大家多看看就行了
		if (arr[mid] > arr[end])
			return mid;
		else if (arr[begin] > arr[end])
			return end;
		else
			return begin;
	}
	else{

		if (arr[mid] < arr[end])
			return mid;
		else if (arr[begin] < arr[end])
			return end;
		else
			return begin;
	}
}

int partion2(int* arr, int begin, int end){
	
	int mid = getMid(arr, begin, end);
	Swap(arr, begin, mid);
	
	//第一个基准值,也就是初始为坑的位置
	int key = arr[begin];
	while (begin < end){
		//从后往前找到小的
		while (begin < end&&arr[end] >= key)
			--end;
		//进行填坑
		arr[begin] = arr[end];
		//从前往后找大的
		while (begin < end&&arr[begin] <= key)
			++begin;
		//填坑
		arr[end] = arr[begin];
	}
	arr[begin] = key;
	return begin;
}


//主要的对于下面写的划分方式的调用
void quickSort(int* arr, int begin, int end){

	if (begin >= end)						//如果开始的 大于结束的则为越界,直接结束
		return;
		
	//div:一次划分后基准值
	//int div = partion(arr, begin, end);
	//int div = partion2(arr, begin, end);
	int div = partion3(arr, begin, end);		//对下面两个种方法进行调用
	//分别进行左右两边的快排
	//[begin,div-1]
	//[div+1,end]
	quickSort(arr, begin, div - 1);			//对上面划分的左右两个区间进行循环调用
	quickSort(arr, div + 1, end);
}

3.前后指针法

//获取中间值的函数接口
int getMid(int* arr, int begin, int end){

	int mid = begin + (end - begin) / 2;	//获取中间值

	if (arr[begin] > arr[mid]){		//对所处的位置进行比较,最终获取中间的值
									//很简单的,我就不具体解释了,大家多看看就行了
		if (arr[mid] > arr[end])
			return mid;
		else if (arr[begin] > arr[end])
			return end;
		else
			return begin;
	}
	else{

		if (arr[mid] < arr[end])
			return mid;
		else if (arr[begin] < arr[end])
			return end;
		else
			return begin;
	}
}

int partion3(int* arr, int begin, int end){
	
	int mid = getMid(arr, begin, end);
	Swap(arr, begin, mid);
	//上一个小于基准值的位置
	int prev = begin;
	//下一个小于基准值的位置
	int cur = begin + 1;

	int key = arr[begin];
	while (cur <= end){
		
		//当cur走到下一个基准值的位置的时候,判断是否连续
		if (arr[cur]<key && ++prev != cur) {
			
			//不连续,交换数据
			Swap(arr, prev,cur);
		}
		++cur;		//cur继续向下走,prev不动
	}
	Swap(arr, begin, prev);		//与基准值进行交换,且固定
	return prev;
}

//主要的对于下面写的划分方式的调用
void quickSort(int* arr, int begin, int end){

	if (begin >= end)						//如果开始的 大于结束的则为越界,直接结束
		return;
		
	//div:一次划分后基准值
	//int div = partion(arr, begin, end);
	//int div = partion2(arr, begin, end);
	int div = partion3(arr, begin, end);		//对下面两个种方法进行调用
	//分别进行左右两边的快排
	//[begin,div-1]
	//[div+1,end]
	quickSort(arr, begin, div - 1);			//对上面划分的左右两个区间进行循环调用
	quickSort(arr, div + 1, end);
}

七.归并排序 (MergeSort)

1.递归实现

//归并排序

void merge(int* arr, int begin, int mid, int end, int* tmp){
	
	//递增
	int begin1 = begin;		//前半个区间
	int end1 = mid;
	
	int begin2 = mid + 1;	//后半个区间
	int end2 = end;

	//辅助空间
	int idx = begin;
	
	//合并有序的序列
	//检查是否越界
	while (begin1 <= end1&&begin2 <= end2){		//如果没有越界
		
		if (arr[begin1] <= arr[begin2])			//如果第一个元素小
			tmp[idx++] = arr[begin1++];			//先将第一个输入

		else
			tmp[idx++] = arr[begin2++];			//否则,先将第二个输入
	}
	//上面的循环进行,直到结束
	
	//判断是否有没有合并的元素
	if (begin1 <= end1)		//如果1中有剩余
	
		//直接将1中剩余的元素拷贝到序列中
		memcpy(tmp + idx, arr + begin1, sizeof(int)*(end1 - begin1 + 1));
	
	if (begin2 <= end2)
		//如果2中有剩余,直接将2中的拷贝过去
		memcpy(tmp + idx, arr + begin2, sizeof(int)*(end2 - begin2 + 1));

	//合并后的序列拷贝到原始的数据
	memcpy(arr + begin, tmp + begin, sizeof(int)*(end - begin + 1));
}

//======================递归
void _mergeSort(int* arr, int begin, int end, int* tmp){
	
	if (begin >= end)	//判空
		return;

	int mid = begin + (end - begin) / 2;		//找到中间元素

	//先进行子序列的合并
	_mergeSort(arr, begin, mid, tmp);
	_mergeSort(arr, mid + 1, end, tmp);
	//上面两部是对于子序列的合并,也就是将一个个的小点点进行合并

	//合并两个有序的子序列
	merge(arr, begin, mid, end, tmp);
}
void mergeSort(int* arr, int n){
	
	//申请辅助空间
	int* tmp = (int*)malloc(sizeof(int)*n);		//申请空间方便存放
	_mergeSort(arr, 0, n - 1, tmp);		//调用函数
	
	free(tmp);		//将申请用完的空间进行释放
}

2.非递归实现

//归并排序

void merge(int* arr, int begin, int mid, int end, int* tmp){
	
	//递增
	int begin1 = begin;		//前半个区间
	int end1 = mid;
	
	int begin2 = mid + 1;	//后半个区间
	int end2 = end;

	//辅助空间
	int idx = begin;
	
	//合并有序的序列
	//检查是否越界
	while (begin1 <= end1&&begin2 <= end2){		//如果没有越界
		
		if (arr[begin1] <= arr[begin2])			//如果第一个元素小
			tmp[idx++] = arr[begin1++];			//先将第一个输入

		else

以上是关于面试必会八大排序算法(Python)的主要内容,如果未能解决你的问题,请参考以下文章

八大排序总结---- 数据结构 (图解法) 面试必会! ! !

八大排序总结---- 数据结构 (图解法) 面试必会! ! !

八大排序总结---- 数据结构 (图解法) 面试必会! ! !

八大排序总结---- 数据结构 (图解法) 面试必会! ! !

面试必会排序算法java 实现冒泡排序讲解

《糊涂算法》之八大排序——希尔排序