手把手教你 ,带你彻底掌握八大排序算法【数据结构】

这篇具有很好参考价值的文章主要介绍了手把手教你 ,带你彻底掌握八大排序算法【数据结构】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


手把手教你 ,带你彻底掌握八大排序算法【数据结构】

插入排序

直接插入排序

直接插入排序是一种简单的插入排序法,其基本思想:是把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列
可以理解为一遍摸扑克牌,一边进行排序

在待排序的元素中,假设前面n-1(其中n>=2)个数已经是排好顺序的,现将第n个数插到前面已经排好的序列中,然后找到合适自己的位置,使得插入第n个数的这个序列也是排好顺序的。
按照此法对所有元素进行插入,直到整个序列排为有序的过程,称为插入排序

  • 当插入第n个元素时,前面的n-1个数已经有序
  • 用第n个数与前面的n-1个数比较,我们将第n个数假设为tmp ,也就是说将tmp插入到[0 ,end] 的区间中
    第一种情况:如果tmp比end大就放在end后面
    第二种情况:如果tmp比end小但是比数组前面的几个数据大,插入之后,tmp之后的数据就往后挪动
    第三种情况:如果tmp比数组所有元素都小,所有数据都需要挪动,end就得挪动到数组下标为-1的位置才结束

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

动图演示:
手把手教你 ,带你彻底掌握八大排序算法【数据结构】

void InsertSort(int* a , int n )
{
	for (int i = 1; i <n ; i++)
	{
		//一趟插入排序 

		int tmp = a[i];
		int end = i-1; //end是数组下标
		while (end >= 0)
		{
			//如果tmp比end小但是比数组前面的几个数据大,插入之后,tmp之后的数据就往后挪动
			if (tmp < a[end])
			{
				a[end + 1] = a[end];//往后挪动
				end--;
			}
			else
			{
				break;
			}

		}
		//如果tmp比数组所有元素都小,所有数据都需要挪动,end就得挪动到数组下标为-1的位置才结束
			//如果tmp比end大就放在end后面
		a[end + 1] = tmp;//包含上述两种情况
   }
}

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

希尔排序

第一步先选定个小于n的数字作为gap,将所有距离为gap的数分为一组进行预排序,预排序的目的就是使数组接近有序,与直接插入排序相同,直接插入排序的间隔为1,预排序的间隔变为gap了
再选一个小于gap的数作为新的gap,重复第一步的操作
当gap的大小减到1时,就相当于整个序列被分到一组,进行一次直接插入排序,排序完成

举例分析一下:

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

我们用序列长度的一半作为第一次排序时gap的值,此时相隔距离为5的元素被分为一组(共分了5组,每组有2个元素),然后分别对每一组进行直接插入排序

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

gap的值折半,此时相隔距离为2的元素被分为一组(共分了2组,每组有5个元素),然后再分别对每一组进行直接插入排序

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

gap的值再次减半,此时gap减为1,即整个序列被分为一组,进行一次直接插入排序。

为什么是选一个小于gap的数作为新的gap,也就是要gap由大变小?
gap越大,数据跳跃的幅度越大,数据挪动得越快;gap越小,数据跳跃的幅度越小,数据挪动得越慢。前期让gap较大,可以让数据更快得移动到自己对应的位置附近,减少挪动次数,提高算法效率

void ShellSort(int* a, int n) // 希尔排序 
{
	int gap = n;
	while (gap > 1)
	{
		//一趟排序
		gap /= 2;
		for (int i = gap; i < n; i += gap)
		{

			int end = i - gap;  //有序序列最后一个元素的下标
			int tmp = a[end + gap];
			while (end >= 0)
			{
				//如果tmp比end小但是比数组前面的几个数据大,插入之后,tmp之后的数据就往后挪动
				if (tmp < a[end])
				{
					a[end + gap] = a[end]; //往后挪动 
					end -= gap;
				}
				else
				{
					break;
				}

			}
			//tmp比数组中所有数据都小  ,一直往后挪动 ,end挪到了-1
			//tmp >a[end] 
			a[end + gap] = tmp;

		}
	}
}

选择排序

选择排序

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

在元素集合array[i]到array[n-1]中选择关键码最大(小)的数据元素;

若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换;

在剩余的array[i] 到 array[n-2](array[i+1]–array[n-1])集合中,重复上述步骤,直到集合剩余1个元素

注意特殊情况 :如果Maxi在left位置,当a [ Mini ] 和 a [ left ] 互换时,此时Maxi的位置就变成了Mini, 我们添加一个判断条件 ,判断left == Maxi , 修正Maxi ,防止Maxi更改
手把手教你 ,带你彻底掌握八大排序算法【数据结构】

以升序为例做分析:

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

经过第一躺交换,最小的元素排在了数组的第一个位置

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

经过第二趟交换 ,第二小的元素已经到了数组第二个元素位置

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

经过第三趟排序 ,第三小的元素已经排在了第三个位置

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

最后一趟排序 ,数组已经有序了

动图演示 :
手把手教你 ,带你彻底掌握八大排序算法【数据结构】

代码实现:

void SelectSort(int* a, int n)//选择排序
{
	//升序 
	int left = 0, right = n - 1;
	while (left < right)
	{
		

	//选出最大最小数的下标 
	
		int Mini = left;
		int Maxi = left;
		//一趟 
		for (int i = left+1 ; i <= right; i++)
		{
			if (a[i] < a[Mini])
			{
				Mini = i; // 更新Mini
			}
			if (a[i] > a[Maxi])
			{
				Maxi = i; //更新Maxi 
			}
		}
	
		// Mini和左边交换
		Swap(&a[left], &a[Mini]);
		// Maxi 在左边  ,Maxi 被换成Mini, 修正Maxi
		if (left == Maxi)
		{
			Maxi = Mini;  
		}
		//Maxi与右边交换
		Swap(&a[right], &a[Maxi]);
		left++;
		right--;
	}
}

选择排序效率较低,实际中很少使用
时间复杂度:O(N^2)
空间复杂度:O(1)
稳定性:不稳定

堆排序

排降序 建立小堆
排升序 建立大堆

升序

向上调整建堆,时间复杂度为O(N* longN)

使用向上调整算法建大堆,将数组建成大堆后,此时堆顶元素是最大的 ,将堆顶元素和最后一个元素进行交换,这样最大的元素就到了数组最后一个元素,对剩下的元素使用向下调整 , 当下一次向下调整时,我们不管这个处在数组最后一个位置的最大元素(有点类似堆的删除 ),此时第二大的元素来到的堆顶 ,堆顶元素继续与最后一个元素进行交换,(注意第一个交换过去的最大的元素已经不在范围内了) ,依次类推 ,升序就完成了

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

 void HeapSort(int* a, int n)
 {
	 //向上调整建堆
	 for (int i = 0; i < n; i++)
	 {
		 AdjustUp(a, i);
	}
	 //向下调整排序
	 int end = n - 1;// end 是最后一个元素的下标
	 while (end > 0)
	 {
		 Swap(&a[0], &a[end]);
		 AdjustDown(a, end, 0);
		 end--;
	 }
 }
 int main()
 {
	 int a[10] = { 2, 1, 5, 7, 6, 8, 0, 9, 4, 3 };
	 HeapSort(a, 10);
	 return 0; 
 }

向下调整建堆的前提是左右子树都是堆 ,从倒数第一个非叶子节点开始倒着调整,如何找到倒数第一个非叶子节点?通过最后一个节点的父节点来找到 , 那为什么要找倒数第一个非叶子节点? 因为倒数第一个非叶子节点的左右子树都满足大堆或小堆的条件

手把手教你 ,带你彻底掌握八大排序算法【数据结构】
手把手教你 ,带你彻底掌握八大排序算法【数据结构】

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

 void HeapSort(int* a, int n)
 {
	 //向下调整建堆
	 for (int i = (n-1-1)/ 2; i >= 0; i--) // n-1是最后一个节点的下标,(n-1-1)/2 通过下标找到最后一个节点的父节点
	 {
		 AdjustDown(a,n , i);
	 }
	 //向下调整排序
	 int end = n - 1; //end 是最后一个元素的下标 
	 while (end >=0)
	 {
		 Swap(&a[0], &a[end]);
		 AdjustDown(a, end, 0);
		 end--;
	 }

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

交换排序

冒泡排序

以升序为例

冒泡排序,该排序的命名非常形象,即一个个将气泡冒出。冒泡排序一趟冒出一个最大(或最小)值
如果前一位的数字大于后一位的,那么这两个数字交换位置,因此,最大的数字在第一轮循环中不断像一个气泡一样向上冒

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

动图演示:
手把手教你 ,带你彻底掌握八大排序算法【数据结构】
代码实现 :

void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n-1; i++)
	{
		//一趟 
		for (int j = 0; j < n-1 - i; j++)
		{
			assert(j < n-1);
			if (a[j] > a[j + 1])
			{
				assert(j + 1 < n);
				Swap(&a[j], &a[j + 1]);
				
			}

		}
	}
}

如果一趟排序没有交换就说明已经有序了,不用再进行比较了
冒泡排序:
时间复杂度 :O(N^2)

快速排序

递归

hoare版本

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

单趟排序 : 一趟下来的结果是让Key左边的数据全部都小于Key,Key右边的数据全部都大于Key, 就相当于Key这个基准值被排好序了,快速排序的单趟排序本质上就是在排一个数的顺序

步骤:

1 选最左边的或者最右边的当作基准值Key
2 定义一个Left和一个Right,Right从右向左走, 若Right遇到小于Keyi的数,则停下,Left从左向右开始走,直到Left遇到一个大于Keyi的数时,将Left和Right的内容交换 ,Right再次开始走,如此进行下去,直到Left和Right最终相遇,此时将相遇点的内容与key交换即可
需要注意的是:若选择最左边的数据作为基准值Key,则需要Right先走;若选择最右边的基准值数据作为基准值Key,则需要Left先走

注意:Left 和Right相遇位置一定比Key小

3 然后我们再将Key的左序列和右序列再次进行这种单趟排序,如此反复操作下去 ,其实就是一个递归

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

如果选择最左边的数据作为基准值Key ,为什么不能Left先走呢?

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

快排hoare版本 单趟动图演示:
手把手教你 ,带你彻底掌握八大排序算法【数据结构】
代码实现:

void QuickSort(int* a, int left , int right ) // Hoare 版本 
{
	int begin = left;
	int end = right;
	
	
	//升序 
	  // 从左向右走 , 从右向左走 
	int Keyi = left; // 最左边为基准值 
	if (left >= right)
		return;   


	while (left <right )
	{
		//单趟排序 
		
		 //右边找小 
		while (right > left && a[right] >= a[Keyi])
		{

			right--;
			assert(right > 0);
		}
		 //左边找大
		while (left < right && a[left] <= a[Keyi])
		{
			left++;
			assert(left <= right);
	     }
		Swap(&a[left], &a[right]);
		
	}
	Swap(&a[Keyi], &a[left]); //Left和Right最终相遇,此时将相遇点的内容与key交换即可

	//递归 类似二叉树的前序遍历 
	  // [begin , Keyi-1 ]  Keyi  [Keyi+1 , end] 
	
	Keyi = left;
	assert(Keyi - 1 >= 0);

 QuickSort( a,  begin, Keyi -1);
 QuickSort(a, Keyi+1, end );

}

快排时间复杂度 :
如果每趟排序所选的Key都正好是该序列的中间值,即单趟排序结束后Key位于序列正中间,那么快速排序的时间复杂度就是O(NlogN)

挖坑法

以升序为例

单趟排序:一趟下来的结果是让Key左边的数据全部都小于Key,Key右边的数据全部都大于Key

步骤(在最左边挖坑为例) :

1 选出最左边或者最右边的一个数 ,存放在Key变量中,并且在该数据位置形成一个坑

2 定义一个Left和一个Right,Left从左向右走,找比Key大的数 ,Right从右向左走,找比Key小的数。(注意: 如果是在最左边挖坑,则需要Right先走;如果是在最右边挖坑,则需要Left先走

3 因为是以最左边挖坑为例 ,所以是Right 先走 , 在Left和Right 遍历的过程中,如果Right遇到小于Key的数,则将该数放到最左边的坑位,并在Right当前位置形成新的坑位,这时Left再向右边走,若遇到大于Key的数,则将其抛入到刚才在Right位置形成的一个坑位,然后在Left 当前位置形成一个坑位,如此循环下去,直到最终Left和Right相遇,而且Left和Right相遇一定相遇在坑位处 ,这时将Key填在坑位即可。

Key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作

单趟动图演示:
手把手教你 ,带你彻底掌握八大排序算法【数据结构】
代码实现:

//快排挖坑法
void QuickSort(int* a, int left, int right)
{
			int begin = left;
		    int end = right;
			if (left >= right)
				return;   

	 // 升序	,最左边挖坑 ,右边先走
	//选最左边为 Key,并形成hole 
	int hole=left;
	
	int Key = a[left];


	while (left < right)
	{
		//单趟排序 
		
		//right 找小 
		while (left < right && a[right] > Key)
		{
			right--;
		}
		//找到比Key小的数 ,放进最左边的坑位 ,并在right当前位置形成新的坑位
		a[hole] = a[right];
		hole = right;
		assert(hole >= 0);

		//left 找 大  
		while (right > left && a[left] < Key)
		{
			left++;
		}
		// 找到比Key大的数 ,放进right当时形成的坑位 ,并在Left当前位置形成新的坑位 
		a[hole] = a[left];
		hole = left;
		assert(hole >= 0);

	}

	//直到最终Left和Right相遇,这时将Key填在坑位即可。
	assert(hole >= 0);
	a[hole] = Key;
	//递归 

	
		//递归 类似二叉树的前序遍历 
	  // [begin , Key-1 ]  Key  [Key+1 , end] 
	
	Key = left;
	assert(Key - 1 >= 0);

 QuickSort( a,  begin, Key -1);
 QuickSort(a, Key+1, end );

}
前后指针版本

以升序为例

单趟排序:一趟下来的结果是让Key左边的数据全部都小于Key,Key右边的数据全部都大于Key

步骤(以最左边为Key 为例) :

1 选最左边的或者最右边的当作基准值Key
2 定义一个prev 和 cur ,prev指针指向序列开头 ,prev 从左往右遍历 ,cur指针指向prev+1, cur从左往右遍历 。
3、若cur指向的内容小于Key,则prev先++,然后交换prev和cur的值,然后cur++;若cur的值大于key,则cur指针直接++。如此进行下去,直到cur指针越界,此时将Key和prev指针指向的内容交换即可。

然后我们再将Key的左序列和右序列再次进行这种单趟排序,如此反复操作下去 ,其实就是一个递归

手把手教你 ,带你彻底掌握八大排序算法【数据结构】
单趟动图演示:
手把手教你 ,带你彻底掌握八大排序算法【数据结构】

代码实现:

int  PartSort2(int* a, int left, int right)  //前后指针法
{

	int begin = left;
	int end = right;
	//升序 
	// 单趟排序
	int prev = left;
	int cur = prev + 1;

	int Keyi = left; //选最左边为Key 

	//cur 找比Key小的 
	while (cur <= right)  //cur未越界就继续 
	{
		//如果cur找到比Key小 ,往后++
		if (a[cur] < a[Keyi] && ++prev != cur) //++prev !=cur , 有可能数组前几个元素比Key小,但是没必要交换
		{
			Swap(&a[cur], &a[prev]); //交换 
		 }
		cur++;  
	}
	assert(cur <= right+1); 
	Swap(&a[prev], &a[Keyi]);
	
	/*递归 类似二叉树的前序遍历 
   [begin , Keyi-1 ]  Keyi  [Keyi+1 , end] */

	Keyi = prev;
	assert(Keyi - 1 >= 0);


	
	return prev;
}
void QuickSort(int* a, int left, int right)
{
   //递归 
	if (left >= right)
		return;

	int keyi = PartSort2(a, left, right);


	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}

非递归

将一个用递归实现的算法改为非递归时,一般需要借用一个数据结构,那就是栈。将Hoare版本、挖坑法以及前后指针法的快速排序改为非递归版本
Hoare版本、挖坑法和前后指针法 ,这三种排序都只写单趟 ,后面再写一个非递归的快速排序,在函数内部调用单趟排序的函数

Hoare

单趟排序代码实现:

//Hoare版本(单趟排序)
int PartSort1(int* a, int left, int right)
{
	int keyi = left;//key的下标
	while (left < right)
	{
		//right走,找小
		while (left < right&&a[right] >= a[keyi])
		{
			right--;
		}
		//left先走,找大
		while (left < right&&a[left] <= a[keyi])
		{
			left++;
		}
		if (left < right)
		{
			Swap(&a[left], &a[right]);//交换left和right的值
		}
	}
	int meeti = left;//L和R的相遇点
	Swap(&a[keyi], &a[meeti]);//交换key和相遇点的值
	return meeti;//返回相遇点,即key的当前位置
}

挖坑法

单趟排序代码实现:

//挖坑法(单趟排序)
int PartSort2(int* a, int left, int right)
{
	int key = a[left];//在最左边形成一个坑位
	while (left < right)
	{
		//right向左,找小
		while (left < right&&a[right] >= key)
		{
			right--;
		}
		//填坑
		a[left] = a[right];
		//left向右,找大
		while (left < right&&a[left] <= key)
		{
			left++;
		}
		//填坑
		a[right] = a[left];
	}
	int meeti = left;//L和R的相遇点
	a[meeti] = key;//将key抛入坑位
	return meeti;//返回key的当前位置
}

前后指针

单趟排序代码实现:

//前后指针法(单趟排序)
int PartSort3(int* a, int left, int right)
{
	int prev = left;
	int cur = left + 1;
	int keyi = left;
	while (cur <= right)//当cur未越界时继续
	{
		if (a[cur] < a[keyi] && ++prev != cur)//cur指向的内容小于key
		{
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	int meeti = prev;//cur越界时,prev的位置
	Swap(&a[keyi], &a[meeti]);//交换key和prev指针指向的内容
	return meeti;//返回key的当前位置
}

快排的优化

三数取中法选key

如果这个序列是非常无序,快速排序的效率是非常高的 ,一旦序列有序 ,每次选取最左边或是最右边的数作为基准值Key,时间复杂度就会从O(N*logN)变为O(N^2),这样快排的效率极低

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

也就是说影响快排时间复杂度就是基准值Key的选取,如果选取的Key离中间位置越近,则效率越高

为了解决这个问题 ,也就出现了三数取中 :
三数取中,当中的三个数指的是:最左边的数、最右边的数以及中间位置的数
三数取中就是取这三个数当中,值的大小居中的那个数作为该趟排序的Key ,因为它离中间位置最近。这就确保了我们所选取的数不会是序列中的最左边的数、最右边的数

手把手教你 ,带你彻底掌握八大排序算法【数据结构】
代码实现:

//三数取中  ,得到中间元素的下标
int GetMiddleIndexi(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] < a[mid])
	{
		if (a[right] > a[mid])
		{
			return mid;// a[right] > a[mid] > a[left]
		}
		// a[mid]>=a[right] ,此时a[mid]是最大的 ,只需要比较a[left] ,a[right] 
		else if (a[left] > a[right])// a[mid] > a[left] > a[right] 
		{
			return left;
		}
		else //a[left] < a[right]
		{
			return right; // a[mid] > a[right] > a[left]
		}

	}
	else //a[left] >= a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		 }
		//a[mid] <= a[right]  ,此时a[mid]已经是最小的 只需要比较a[left] 和a[right]
		else if (a[left] > a[right]) //  a[left] > a[right] > a[mid] 
		{
			return right; 
		}
		else // a[left] <= a[right]
		{
			return left;   //a[right] > a[left] > a[mid ]
		}
	}

}
递归到小的子区间时,可以考虑使用插入排序

归并排序

归并排序是采用分治法的一个非常典型的应用。其基本思想是:将已有序的子序合并,从而得到完全有序的序列,即先使每个子序有序,再使子序列段间有序
手把手教你 ,带你彻底掌握八大排序算法【数据结构】

动图演示:
手把手教你 ,带你彻底掌握八大排序算法【数据结构】

递归实现

核心步骤:

分解得到子序列
如何得到有序的子序列 ? 序列分解到只有一个元素或是没有元素时,就可以认为是有序了
然后合并两个有序的子序列 ,思路与Leetcode. 88合并两个有序数组 相似 ,依次比较 ,较小值尾插到tmp数组中
创建一个与待排序列大小相同的tmp数组 ,合并完毕后再将tmp数组里的数据拷贝回原数组 ,最后将tmp 数组释放 。
代码实现 :

void _MergeSort(int* a, int left, int right,  int * tmp)
{
	int mid = (left + right) / 2;

	//分割
	 // [begin , mid]  [mid+1 , end ]
	int begin = left;
	int end = right; 
	if (begin >= end)//序列分解到只有一个元素或是没有元素时,就可以认为是有序了
		return;

	_MergeSort(a, begin, mid, tmp); //end-begin+1 是元素个数 
	_MergeSort(a, mid + 1, end, tmp);

	//归并 ——类似合并两个有序数组 ,[begin , mid]  [mid+1 , end ]
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;  //不能取0 , 两段有序区间不一定是从头开始的区间 
	
	while (begin1 <= end1 && begin2 <=end2 ) 
	{
		if (a[begin1] < a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];

		}
	}
	//begin1 先走完 ,begin2还没有走完,begin2尾插到tmp数组后面
	while (begin2 <= end2 )
	{
		tmp[i++] = a[begin2++];
	}
	//begin2 先走完 ,begin1还没有走完, begin1尾插到tmp数组后面 
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	//将tmp数组拷贝到原数组中 
	memcpy(a+begin, tmp+begin, sizeof(int*) * ( right- left +1));
}
void MergeSort(int* a , int left , int right , int *tmp  )
{
	//申请一个与原数组大小的tmp数组 
   tmp = (int*)malloc(sizeof(int) *  (right -left +1 ) );
	
	//归并
	
	_MergeSort( a,  left,  right , tmp);

		//释放tmp 数组 
	free(tmp);

}

tmp 和 a的后面为什么都要加begin ?
如图所示:
手把手教你 ,带你彻底掌握八大排序算法【数据结构】

非递归实现

递归实现的缺点就是会一直调用栈帧,而栈帧内存往往是很小的。所以,我们尝试着用循环的办法去实现,也就是用非递归的方式去实现 ,但是非递归需要处理几个边界条件

和递归的区别就是递归要将区间一直细分,要将左区间一直递归划分完了,再递归划分右区间,而借助数组的非递归是一次性就将数据处理完毕,并且每次都将下标拷贝回原数组

归并排序的非递归基本思路是将待排序序列a[0…n-1]看成是n个长度为1的有序序列,将相邻的有序表成对归并

非递归的核心就是需要控制每次参与合并的元素个数,使序列变为有序

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

如果排序的数是奇数,就会出现一些越界的情况需要特殊处理

end1越界,begin2 和end2绝对也越界了 ,那就不对end1后面的进行归并
手把手教你 ,带你彻底掌握八大排序算法【数据结构】
举例
手把手教你 ,带你彻底掌握八大排序算法【数据结构】


begin2 和end2越界,那不需要对begin2和end2之间的数进行归并
手把手教你 ,带你彻底掌握八大排序算法【数据结构】
举例
手把手教你 ,带你彻底掌握八大排序算法【数据结构】

end2 越界,还是需要归并,修正end2就可以了
手把手教你 ,带你彻底掌握八大排序算法【数据结构】
举例:
手把手教你 ,带你彻底掌握八大排序算法【数据结构】

void MergeSort(int* a,int  n )
{
	int* tmp = (int*)malloc(sizeof(int) * (n ) );
	if (tmp == NULL)
	{
		printf("malloc fail");
		exit(-1);
	}
	int gap = 1;
	
	while (gap<n)
	{
		//归并
		for (int i = 0; i < n; i += 2 * gap)
		{
			//归并 ——类似合并两个有序数组 ,[begin , end1]  [begin2 , end2 ]
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap - 1 + 1, end2 =i+ 2 * gap - 1;  //从图中分析可得

			int index= i;
			printf("[%d %d] [%d %d] ", begin1, end1, begin2, end2);
			/*end1 和begin2越界*/
			if (end1 >=n || begin2 >=n)
			{
				break;
			}
			//end2越界,修正
			if (end2 >=n)
			{
				end2 = n - 1;
			}
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];

				}
			}
			//begin1 先走完 ,begin2还没有走完,begin2尾插到tmp数组后面
			while (begin2 <= end2)
			{
				tmp[index++] = a[begin2++];
			}
			//begin2 先走完 ,begin1还没有走完, begin1尾插到tmp数组后面 
			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}
			//将tmp数组拷贝到原数组中 
			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		
		gap *= 2;
	}
	free(tmp);
}

归并排序时间复杂度:

每一层归并排序的消耗是O(N) ,有 log层 , 所以归并排序的时间复杂度 O(N * logN)

排序算法复杂度以及稳定性

手把手教你 ,带你彻底掌握八大排序算法【数据结构】
手把手教你 ,带你彻底掌握八大排序算法【数据结构】

稳定的排序有:直接插入排序、冒泡排序、归并排序
不稳定的排序有:希尔排序、选择排序、堆排序、快速排序、计数排序

手把手教你 ,带你彻底掌握八大排序算法【数据结构】

如果你觉得这篇文章对你有帮助,不妨动动手指给点赞收藏加转发,给鄃鳕一个大大的关注
你们的每一次支持都将转化为我前进的动力!!!
文章来源地址https://www.toymoban.com/news/detail-431021.html

到了这里,关于手把手教你 ,带你彻底掌握八大排序算法【数据结构】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 手把手教你暴力破解

    暴力破解是一种攻击手段,使用大量的认证信息在认证接口尝试登录,直到得到正确的结果。 2.1标题基于表单的暴力破解 2.1.1 第一步:打开burpsuite拦截 2.1.2 第二步:将拦截到的包右击发送到intruder模块 (其中简单介绍一下intruder模块) Target主要是设置暴力破解访问的host地址

    2024年02月07日
    浏览(62)
  • 手把手教你落地DDD

    一、前言 常见的DDD实现架构有很多种,如经典四层架构、六边形(适配器端口)架构、整洁架构(Clean Architecture)、CQRS架构等。架构无优劣高下之分,只要熟练掌握就都是合适的架构。本文不会逐个去讲解这些架构,感兴趣的读者可以自行去了解。 本文将带领大家从日常的

    2024年02月16日
    浏览(54)
  • 手把手教你实战TDD

    领域驱动设计,测试驱动开发。 我们在《手把手教你落地DDD》一文中介绍了领域驱动设计(DDD)的落地实战,本文将对测试驱动开发(TDD)进行探讨,主要内容有:TDD基本理解、TDD常见误区、TDD技术选型,以及案例实战。希望通过本文,读者能够理解掌握TDD并将其应用于实际

    2024年02月08日
    浏览(51)
  • 手把手教你做主成分分析

    主成分分析是一种降维处理的统计方法,实践中有三个应用场景: 信息浓缩:将多个分析项浓缩成几个关键概括性指标; 权重计算:利用方差解释率值计算各概括性指标的权重; 综合评价:基于主成分得分构造综合得分数据,用于综合评价。 接下来,以一个具体案例来学习

    2024年02月01日
    浏览(61)
  • 手把手带你搞懂AMS启动原理

    彻底搞懂AMS即ActivityManagerService,看这一篇就够了 最近那么多教学视频(特别是搞车载的)都在讲AMS,可能这也跟要快速启动一个app(甚至是提高安卓系统启动速度有关),毕竟作为安卓系统的核心系统服务之一,AMS以及PMS都是很重要的,而我之前在 应用的开端–PackageManag

    2024年02月12日
    浏览(123)
  • 手把手教你用代码画架构图

    作者:京东物流 覃玉杰 本文将给大家介绍一种简洁明了软件架构可视化模型——C4模型,并手把手教大家如何使用 代码 绘制出精美的C4架构图。 阅读本文之后,读者画的架构图将会是这样的: 注:该图例仅作绘图示例使用,不确保其完整性、可行性。 C4是软件架构可视化

    2024年02月04日
    浏览(53)
  • 手把手教你爬取网站信息

    如题,理解这一部分需要一定的Python基础,有些代码我不做详细解释了,但是用这个方法是确实可以爬到的。 1. 在抓包⼯具中先定位到和浏览器地址栏的⽹址⼀样的数据包 ①在页面中右击鼠标,点击检查,博主这里用的是Google浏览器 ②在弹出来的页面中点击Network,然后再重

    2024年02月02日
    浏览(42)
  • 手把手教你绘制小程序海报

    海报分享功能在许多应用中应该是很常见的,因为它作为一种常用的应用推广和拉新的方式。 接下来看个实际的案例,如下: 把任务拆解下: 如何绘制海报 如何把绘制后的海报保存到相册 用 canvas 来绘制海报。 这里需要了解基本的 canvas api ,不熟悉可以先去了解下相关

    2024年02月04日
    浏览(53)
  • 手把手教你kali渗透Metasploitable

    2.ifconfig查看Metasploitable的IP: 端口爆破:FTP、SSH等 工具:Hydra 备用字典:user.txt: pass,txt: 一、 爆破FTP //注意路径是桌面还是Desktop 二、 爆破SSH 利用爆破得到的用户名和密码,进行远程连接测试: 成功连接: 三、端口渗透: 利用metasploit 然后在kali linux中进行渗透: 启动msf msf

    2024年02月06日
    浏览(44)
  • 手把手教你玩Hugging Face

    Hugging Face起初是一家总部位于纽约的聊天机器人初创服务商,他们本来打算创业做聊天机器人,然后在github上开源了一个Transformers库,虽然聊天机器人业务没搞起来,但是他们的这个库在机器学习社区迅速大火起来。目前已经共享了超100,000个预训练模型,10,000个数据集,变成

    2024年02月06日
    浏览(55)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包