10大经典排序算法

10大经典排序算法

冒泡排序

简介

冒泡排序是因为越小的元素会经由交换以升序或降序的方式慢慢浮到数列的顶端,就如同碳酸饮料中二氧化碳的气泡最终会浮到顶端一样,故名冒泡排序

复杂度与稳定度

在这里插入图片描述

思路原理

以升序为例

  1. 从第一个元素开始一个一个的比较相邻的元素,如果第一个比第二个大,即a[1] > a[2]就彼此交换。
  2. 从第一对到最后一对,对每一对相邻元素做一样的操作。此时在最后的元素应该会是最大的数,我们也称呼一遍这样的操作为一趟冒泡排序
  3. 针对所有的元素重复以上的步骤,每一趟得到的最大值已放在最后,下一次操作则不需要将此最大值纳入计算。
  4. 持续对每次对越来越少的元素,重复上面的步骤。
  5. 直到所有的数字都比较完成符合a[i] < a[i+1],即完成冒泡排序。

图示过程

以数组数据{70,50,30,20,10,70,40,60}为例:

在这里插入图片描述
如图,每一次排序把最大的数被放在了最后,然后按照这个趋势逐渐往前,直到按从小到大的顺序依次排序。

到了第4轮的时候,这个数据已经排序结束了,但此时程序仍然在进行。

直到第5,6轮程序才算真正的结束,这其实是一种浪费算力的表现

代码

`
void vSwap(int &a,int &b)
{
	int temp = 0;
	
	a = b;

	b = temp;

	return ;
}

void vBubbleSort(int pa[], int n)
{
	for(int i = 0; i < n; i++) // 代表已排序好的个数
	{
		for(int j = 0; j < (n - i); j++) // 冒泡交换的次数
		{
			if(pa[j] > pa[j+1])
			{
				vSwap(pa[j],pa[j+1]);
			}
		}
	}
}
`

选择排序

简介

选择排序是一种简单直观的排序算法,它从待排序的数据元素中选出最小或最大的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找最小或最大元素,然后放到已排序的序列的末尾。依次类推,直到全部待排序的数据元素的个数为零。

复杂度与稳定性

在这里插入图片描述

算法分析

对于选择排序首先要理解选择排序的思想,程序中会用到两个for循环,一个for循环用来输入值,确定数据元素的位置;一个for循环用来排序,确定位置上的数据元素与后面待排序区间的数据元素进行比较与交换,根据其大小决定是否进行交换,交换时要用中间值进行交换。

关键字

选择,待排序最小值,记录数据元素位置

过程介绍(顺序为例)

  1. 首先设置两个记录i和j,i从数组第一个元素开始,j从(i+1)个元素开始。
  2. 接着j遍历整个数组,选出整个数组最小的值,并让这个最小的值和i的位置交换。
  3. i选中下一个元素(i++),重复进行每一趟选择排序。
  4. 持续上述步骤,使得i到达(n-1)处,即完成排序。

图示过程

以数据{2,10,9,4,8,1,6,5}为例

在这里插入图片描述
如图所示,每次交换的数据使用红色标记出,已经排好序的数据使用蓝底标注,

每一趟从待排序的数据元素中选出最小的一个元素,顺序放在已排好序的数列的最后,直到全部待排序的数据元素排完。

只需进行n-1趟排序即可,因为剩下的一个数据一定是整体数据中最大的数据。

代码

`
void vSelectSort(int a[], int n)
{

	int temp = 0;

	for(int i = 0; i < n-1; i++)
	{
		temp = i;	// 记录未排序下标

		for(int j = i+1; j < n; j++) // 变量整个数组
		{
			if(a[temp] > a[j])		// 选出最小值
			{
				temp = j;
			}
		}

		swap(a[j],a[temp]);

	}
}	
`

相比冒泡排序的不断交换,简单的选择排序是等到合适的关键字出现后再进行交换,并且交换一次就可以达到一次冒泡的效果。

插入排序

简介

插入排序是一种最简单的排序方法,对于少量元素的排序,它是一个有效的算法。

更加地详细过程参考此处连接:https://blog.csdn.net/hcz666/article/details/126488359https://blog.csdn.net/hcz666/article/details/126488359

复杂度与稳定性

在这里插入图片描述

插入排序的原理与基本思想

插入排序的基本思想是将一个记录插入到已经排好的有序表中,从而的得到一个新的,记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面的有序表进行待插入位置查找,并进行移动

**插入排序的基本思想是:**将未排序的元素一个一个地插入到有序的集合中,插入时把所有有序集合从后向前扫一遍,找到合适的位置插入。

关键字

有序数组集合、插入、从后向前扫描复制

图示过程

插入之后的数组数据{ 70,50,30,20,10,70,40,60}为例:
在这里插入图片描述

将红色的数据依次插入组成一个逐渐有序的数组

代码

`
	void vInsetSort(int a[], int n)
	{
		int i,j;
		int temp;

		// 外层循环标识并决定待比较的数值
		for(i = 1; i < n; i++)	// 循环从第2个元素开始
		{
			if(a[i] < a[i-1])
			{
				// 缓存待比较值
				temp = a[i];
			
				// 从后向前扫描有序数据集合,将待比较数值确定其最终位置
				for(j = i -1; j >= 0 && a[j] > temp; j--)
				{
					a[j+1] = a[j];
				}

				// 此处就是a[j+1] = temp;
				a[j+1] = temp;
			}
		}

	}

`

希尔排序

简介

希尔排序又称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因D.L.Shell于1959年提出而得名。

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

更加地详细过程参考此处连接https://blog.csdn.net/wang20010104/article/details/124880586

复杂度与稳定性

在这里插入图片描述

过程图示

希尔排序目的为了加快速度改进了插入排序,交换不相邻的元素对数组的局部进行排序,并最终用插入排序将局部有序的数组排序。
在此我们选择增量gap = length/2,缩小增量以gag = gap / 2的方式,用{n/2,(n/2)/2…1}来表示。

如图示例:

  • 初始增量第一趟gap = length / 2 = 4

  • 第二趟,增量缩小为2

  • 第三趟,增量缩小为1,得到最终排序结果。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码

`
	希尔排序-插入法

	void vShellSort(int arr[], int n)
	{

		// 分而治之,循环为每次总数除2
		for(int i = (n/2); i > 0; i /= 2)
		{
			// 直接插入排序
			for(int j = i; i < n; j++)
			{
				// 循环分治每一个分组
				int index = j;

				int temp = arr[index];

				// 比较每一组的值
				if(arr[index] < arr[index-1])
				{
					// 从后往前插入,将值插入合适的位置
					while(index - i > 0 && temp < arr[index-i])
					{
						arr[index+1] = arr[index-i];
						
						index -= i;
					}	
					
					arr[index] = temp;

				}
			}

		}		

	}

`

堆排序

简介

堆排序是指利用堆数据结构所设计的一种排序算法,它是一个近似完全二叉树的结构。同时堆满足堆积的性质:即子节点的键值或索引总是小于或大于它的父节点。

复杂度与稳定性

在这里插入图片描述

什么是堆?

由于堆排序比较特殊,我们先了解一下堆是什么?

堆是一种非线性的数据结构,其实就是利用完全二叉树的结构来维护的一维数组,利用这种结构可以快速访问到需要的值,堆可以分为大顶堆和小顶堆。

  • 大顶堆:每个节点的值都大于或等于其左右孩子节点的值
  • 小顶堆:每个节点的值都小于或等于其左右孩子节点的值

过程介绍

首先把待排序的元素按照大小在二叉树位置上排序,且要满足堆的特性,如果要满足的特性,如果根节点存放的是最大的数,则叫做大根堆,反之就叫做小根堆了。

根据这个特性就可以把根节点拿出来,然后再堆化下,即用父节点和他的孩子节点进行比较,取最大的孩子节点和其进行交换,再把根节点拿出来,一直循环到最后一个节点,就排序好了。

主要代码实现

`
	void Swap(int *heap, int len)
	{

		int temp;

		temp = heap[0];		

		heap[0] = heap[len-1];

		heap[len-1] = temp;

	}


	void BuildMaxHeap(int *heap, int len)
	{

		int i, temp;

		for(i = len/2-1; i>=0; i--)
		{

			if((2*i+1) < len && heap[i] < heap[2*i+1]){

				temp = heap[i];

				heap[i] = heap[2*i+1];

				heap[2*i+1] = temp;

				if((2*(2*i+1)+1 < len && heap[2*i+1] < heap[2*(2*i+1)+1])  || (2*(2*i+1)+2 < len && heap[2*i+1] < heap[2*(2*i+1)+2]))
				{
					BuildMaxHeap(heap,len);
				}	
			}

			if((2*i+2) < len && heap[i] < heap[2*i+2])
			{

				temp = heap[i];

				heap[i] = heap[2*i+2];

				heap[2*i+2] = temp;

				if((2*(2*i+2)+1 < len && heap[2*i+2] < heap[2*(2*i+2)+1]) || (2*(2*i+2)+2 < len && heap[2*i+2] < heap[2*(2*i+2)+2]))
				{

					BuildMaxHeap(heap,len);
				}
				
			}
		
		}

	}


`

归并排序

简介

归并排序是建立在归并操作上的一种有效、稳定的排序算法,该算法是采用分治法的一个非常典型的应用,其核心思想是将两个有序的数列合并成一个大的有序序列。

复杂度与稳定性

在这里插入图片描述
注意:归并排序需要创建一个与原数组相同长度的数组来辅助排序。

过程介绍

首先将已有有序的子序列合并,得到完全有序的序列,即先使每个子序列有序,再使子序列段间有序。

过程如下:

1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
4. 重复步骤3直到某一指针超出序列尾;
5. 将另一序列剩下的所有元素直接复制到合并序列尾。

图示过程

在这里插入图片描述

第一次排序将数组分为“两个”一组,组内顺序,其次再追个的将各组进行整合,最终完成归并排序。

主要代码实现

`
void vMerge(int arr[], int l, int mid, int r)
{
	
	// 开辟一个新的数组,将原数组映射进去
	int aux[r-l+1];

	for(int m=l; m <= r; m++)
	{
		aux[m-1] = arr[m];
	}

	// i和j分别指向两个子数组开头部分
	int i = l, j = mid+1;

	for(int k = l; k <= r; k++)
	{
		
		if(i > mid)
		{

			arr[k] = aux[j-1];

			j++;			

		}else if(j > r){

			arr[k] = aux[i-l];

			i++;
	
		}else if(aux[i-l] < aux[j-l]){

			arr[k] = aux[i-l];

			i++;

		}else{
	
			arr[k] = aux[j-l];

			j++;

		}
		
	}

}

void vMerge_Sort(int arr[], int n)
{

	for(int sz = 1; sz <= n; sz += sz)
	{

		for(int i = 0; (i+sz) < n; i += sz + sz)
		{
			// 对局部:arr[i...sz-1]和arr[i+sz.....i+2*sz-1]进行排序
			vMerge(arr,i,i+sz-1,min(i+sz+sz-1,n-1)); // min函数防止越界
		}

	}

}

`

快速排序

简介

快速排序在1960年提出,是考察次数最多的排序,无论是在大学专业课的期末考试,还是在公司的面试测试题中,快速排序都极大地被使用,在实际中快速也极大地被使用。

复杂度与稳定性

在这里插入图片描述

过程介绍

通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

1. 在数组中选择一个基准点
2. 分别从数组的两端扫描数组,设两个指示标志
3. 从后半部分开始,如果发现有元素比该基准点的值小,就交换位置
4. 然后从前半部分开始扫描,发现有元素大于基准点的值,继续交换位置
5. 如此往复循环,然后把基准点的值放到high这个位置,排序完成

以后采用递归的方式分别对前半部分和后半部分排序,当前半部分和后半部分均有序时该数组就自然有序了。

图示过程

在这里插入图片描述

可以看出,在第四趟时就已经达到顺序,但是仍然还是会继续计算几趟直到完成全部运算。

主要代码实现

`
void vQucikSort(int arr[], int low, int high)
{

	int i,j,temp;
	i = low;
	j = high;

	if(low < high)
	{

		temp = arr[low];

		while(i != j)
		{
			while(j > i && arr[i] >= temp)
			{
				--j;
			}

			if(i < j)
			{
				arr[i] = arr[j];

				++i;
			}

			while(i < j && a[i] < temp)
			{

				++i;	
			}

			if(i < j)
			{
				a[j] = a[i];

				--j;
			}

		}

		a[i] = temp;

		vQucikSort(arr,low,i-1);

		vQucikSort(arr, i+1, high);

	}

}
	
`

计数排序

简介

计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

计数排序算法不是基于元素比较,而是利用数组下标来确定元素的正确位置。

它的优势在于对一定范围内的整数排序时,它的复杂度为O(n+k),快于任何比较排序算法。

当然这是一种牺牲控件换取时间的做法,而且当O(k) > O(n*log(n))的时候其效率反而不如基于比较的排序。

复杂度与稳定性

在这里插入图片描述

过程介绍

1. 找出待排序的数组中最大和最小的元素;
2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
3. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

图示过程

如下图,A为待排序的数组,C记录A中各个值得数目。
在这里插入图片描述

将C[i]转换为值小于等于i的元素个数。
在这里插入图片描述

为数组A从后向前的每个元素找到对应的B中的位置,每次从A中复制一个元素到B中,C中相应的计数减一。
在这里插入图片描述

当A中的元素都复制到B中后,B就是排序好的结果,如图所示。
在这里插入图片描述

代码实现

`
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


void vCounterSort(int *arr, int len)
{

	if(arr == NULL) return;

	int max = arr[0],min = arr[0];

	for(int i = 1; i < len; i++)
	{
		if(arr[i] > max) max = arr[i];
		if(arr[i] < min) min = arr[i]; 
	}

	int size = max - min + 1;
	int *pCount = (int *)malloc(sizeof(int)*size);
	memset(pCount, 0, sizeof(int)*size);

	for(int i = 0; i < len; i++)
	{
		// 包含自己
		pCount[arr[i] - min]++;
	}

	for(int i = 0; < size; i++)
	{
		pCount[i] += pCount[i-1];
	}

	int *psort = (int*)malloc(sizeof(int)*len);
	memset(psort, 0, sizeof(int)*len);

	for(int i = len-1; i >= 0; i--)
	{
		pCount[arr[i] - min]--;
		psort[pCount[arr[i] - min]] = arr[i];
	}

	for(int i = 0; i < len; i++)
	{
		arr[i] = psort[i];
	}

	free(pCount);
	free(psort);
	pCount = NULL;
	psort = NULL;


}

桶式排序

简介

桶排序也称箱排序,原理是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。

桶排序是鸽巢排序的一种结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是比较排序,它不受到O(n log n) 下限的影响。

复杂性与稳定性

在这里插入图片描述

过程介绍

1. 根据待排序集合中最大元素和最小元素的差值范围和映射规则,确定申请的桶个数;
2. 遍历待排序集合,将每一个元素移动到对应的桶中;
3. 对每一个桶元素进行排序,并移动到已排序集合中。

过程介绍

元素分布在桶中:
在这里插入图片描述
然后,元素在每个桶中排序:
在这里插入图片描述

代码实现

、

#include<iterator>
#include<iostream>
#include<vector>
using namespace std;
const int BUCKET_NUM = 10;

struct ListNode{
    explicit ListNode(int i=0):mData(i),mNext(NULL){}
    ListNode* mNext;
    int mData;
};

ListNode* insert(ListNode* head,int val){
    ListNode dummyNode;
    ListNode *newNode = new ListNode(val);
    ListNode *pre,*curr;
    dummyNode.mNext = head;
    pre = &dummyNode;
    curr = head;
    while(NULL!=curr && curr->mData<=val){
            pre = curr;
            curr = curr->mNext;
    }
    newNode->mNext = curr;
    pre->mNext = newNode;
    return dummyNode.mNext;
}


ListNode* Merge(ListNode *head1,ListNode *head2){
    ListNode dummyNode;
    ListNode *dummy = &dummyNode;
    while(NULL!=head1 && NULL!=head2){
            if(head1->mData <= head2->mData){
                    dummy->mNext = head1;
                    head1 = head1->mNext;
            }else{
                    dummy->mNext = head2;
                    head2 = head2->mNext;
            }
            dummy = dummy->mNext;
    }
    if(NULL!=head1) dummy->mNext = head1;
    if(NULL!=head2) dummy->mNext = head2;

    return dummyNode.mNext;
}

void BucketSort(int n,int arr[]){
    vector<ListNode*> buckets(BUCKET_NUM,(ListNode*)(0));
    for(int i=0;i<n;++i){
            int index = arr[i]/BUCKET_NUM;
            ListNode *head = buckets.at(index);
            buckets.at(index) = insert(head,arr[i]);
    }
    ListNode *head = buckets.at(0);
    for(int i=1;i<BUCKET_NUM;++i){
            head = Merge(head,buckets.at(i));
    }
    for(int i=0;i<n;++i){
            arr[i] = head->mData;
            head = head->mNext;
    }
}
、

基数排序

简介

基数排序是透过键值的部分资讯,将要排序的元素分配至某些"桶"中,藉以达到排序的作用,在某些时候,基数排序法的效率高于其它的稳定性排序法。

复杂度与稳定性

在这里插入图片描述

图示过程

设有一个初始序列为:R{50, 123, 543, 187, 49, 30, 0, 2, 11, 100}。
在这里插入图片描述

过程介绍

任何一个阿拉伯数,它的各个数上的基数都是以09来表示的。所以我们不妨把09视为10个桶。

  1. 我们先根据序列的个位数的数字来进行分类,将其分到指定的桶中;
  2. 分类后,我们在从各个桶中,将这些数按照从编号0到编号9的顺序依次将所有数取出来;
  3. 得到的序列就是个位数上呈递增趋势的序列;
  4. 按照上图个位数排序:{50, 30, 0, 100, 11, 2, 123, 543, 187, 49};
  5. 接下来对十位数、百位数也按照这种方法进行排序,最后就能得到排序完成的序列。

主要代码实现

`

	public class RadixSort {
    // 获取x这个数的d位数上的数字
    // 比如获取123的1位数,结果返回3
    public int getDigit(int x, int d) {
        int a[] = {1, 1, 10, 100}; // 本实例中的最大数是百位数,所以只要到100就可以了
        return ((x / a[d]) % 10);
    }

  public void radixSort(int[] list, int begin, int end, int digit) {
      final int radix = 10; // 基数-;l0.
      int i = 0, j = 0;
      int[] count = new int[radix]; // 存放各个桶的数据统计个数
      int[] bucket = new int[end - begin + 1];
      // 按照从低位到高位的顺序执行排序过程
      for (int d = 1; d <= digit; d++) {
          // 置空各个桶的数据统计
          for (i = 0; i < radix; i++) {
              count[i] = 0;
          }
          // 统计各个桶将要装入的数据个数
          for (i = begin; i <= end; i++) {
              j = getDigit(list[i], d);
              count[j]++;
          }
          // count[i]表示第i个桶的右边界索引
          for (i = 1; i < radix; i++) {
              count[i] = count[i] + count[i - 1];
          }
          // 将数据依次装入桶中
          // 这里要从右向左扫描,保证排序稳定性
          for (i = end; i >= begin; i--) {
              j = getDigit(list[i], d);
              // 求出关键码的第k位的数字, 例如:576的第3位是5
              bucket[count[j] - 1] = list[i];
              // 放入对应的桶中,count[j]-1是第j个桶的右边界索引
              count[j]--; // 对应桶的装入数据索引减一
          }
          // 将已分配好的桶中数据再倒出来,此时已是对应当前位数有序的表
          for (i = begin, j = 0; i <= end; i++, j++) {
              list[i] = bucket[j];
          }
      }
  }

  public int[] sort(int[] list) {
      radixSort(list, 0, list.length - 1, 3);
      return list;
  }

  // 打印完整序列
  public void printAll(int[] list) {
      for (int value : list) {
          System.out.print(value + "\t");
      }
      System.out.println();
  }

  public static void main(String[] args) {
      int[] array = {50, 123, 543, 187, 49, 30, 0, 2, 11, 100};
      RadixSort radix = new RadixSort();
      System.out.print("排序前:\t\t");
      radix.printAll(array);
      radix.sort(array);
      System.out.print("排序后:\t\t");
      radix.printAll(array);
  }
}


`
  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值