排序算法大杂烩(概念、复杂度、代码实现、应用实例) - Go语言中文社区

排序算法大杂烩(概念、复杂度、代码实现、应用实例)


大部分内容来自于一像素”以及其他的网上信息。待完善(关于不同版本代码的实现以及应用实例)

目录:

一、常见算法分类

二、算法复杂度

三、相关概念

四、算法详情以及代码实现


一、 常见的排序算法可分为两类

非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序。

线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。


二、 复杂度比较



三、 相关概念

稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。

不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。

时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。

空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数


四、 算法详情--非线性时间比较算法

交换排序: 冒泡排序 与 快速排序

4.1 冒泡排序

定义(描述):

它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

算法:

  1. 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
  3. 针对所有的元素重复以上的步骤,除了最后一个;
  4. 重复步骤1~3,直到排序完成。

动画演示:



代码实现(待完善):

C++版本

//冒泡排序
void bubbleSort(int arr[], int n)
{
	for(int i = 0;i < n;i++){  
		//比较两个相邻的元素   
		for(int j = 0;j < n-i-1;j++){  
            if(arr[j] > arr[j+1]){  
                int t = arr[j];  
                arr[j] = arr[j+1];  
                arr[j+1] = t;  
            }  
        }  
    }       
}

Python版本


MATLAB 版本


4.2 快速排序

定义:

其思想是:先选一个“标尺”,用它把整个队列过一遍筛子, 以保证,其左边的元素都不大于它,其右边的元素都不小于它。这样,排序问题就被分割为两个子区间,再分别对子区间排序就可以了。

算法:

快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:

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

重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;

递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

动画演示:


代码实现:

C++版本

//快速排序 
void quickSort(int *arr,int l,int r)
{
	//此处编写代码实现快速排序
	int i,j,x,temp;
	if(l<r)
	{
		i=l;
		j=r;
		x=arr[(l+r)/2]; 
		//以中间元素为轴 
		while(1)
		{
			while(i<=r&&arr[i]<x)
				i++;
			while(j>=0&&arr[j]>x)
				j--;
			if(i>=j) //相遇则跳出 
				break;
			else
			{
				temp=arr[i];
				arr[i]=arr[j];
				arr[j]=temp; 
				//交换 
			}
		}
		quickSort(arr,l,i-1); //对左半部分进行快排 
		quickSort(arr,j+1,r); //对右半部分进行快排 
	}
}


冒泡与快排异同:


插入排序:简单插入 与 希尔排序

4.3 简单插入排序


定义:

插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

算法描述:

  1. 从第一个元素开始,该元素可以认为已经被排序;
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  5. 将新元素插入到该位置后;
  6. 重复步骤2~5。

动画演示:


代码实现:

C++版本

//插入排序
void insertSort(int arr[], int n){  
	for(int i = 1;i < n;i++){  
		int temp = arr[i];  
		int j = i - 1;  
		while(temp < arr[j]){  
			arr[j+1] = arr[j];  
			j--;  
			if(j == -1){  
				break;  
			}  
		}  
		arr[j+1] = temp;  
	}  
}  


4.4 希尔排序

定义:

1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。

算法描述:

选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;

按增量序列个数k,对序列进行k 趟排序;

每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

动画演示:



代码实现:

C++版本

// 希尔排序
void Shellsort(int a[], int n) {
	int i, j, gap;
	for (gap = n / 2; gap > 0; gap /= 2)
	{
		//for (i = 0; i < gap; i++)
		for (i = gap; i < n; i++)
		{
			for (j = i - gap; j >= 0; j -= gap)
			{
				if (a[j + gap] < a[j])
					swap(a[j + gap], a[j]);
			}
		}
	}
}


异同:


选择排序:简单选择 与 堆排序

4.5 简单选择排序

定义:

选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

算法描述:

  1. 初始状态:无序区为R[1..n],有序区为空;
  2. 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
  3. n-1趟结束,数组有序化了。

动画演示:


代码实现:

C++版本

//选择排序
void choiceSort(int arr[], int n){  
	for(int i = 0;i < n; i++){  
		int m = i;  
        for(int j = i + 1;j < n;j++){  
            //如果第j个元素比第m个元素小,将j赋值给m   
			if(arr[j] < arr[m]){  
                m = j;  
            }  
        }  
        //交换m和i两个元素的位置   
		if(i != m){  
            int t = arr[i];  
            arr[i] = arr[m];  
            arr[m] = t;  
		}  
    }  
}


4.6 堆排序

定义:

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

算法描述:

  1. 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
  2. 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
  3. 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

动画演示:


代码实现:

C++版本

//堆排序
void Heapify(int arr[], int first, int end){
    int father = first;
    int son = father * 2 + 1;
    while(son < end){
        if(son + 1 < end && arr[son] < arr[son+1]) ++son;
        //如果父节点大于子节点则表示调整完毕
        if(arr[father] > arr[son]) break;
        else {
         //不然就交换父节点和子节点的元素
            int temp = arr[father];
            arr[father] = arr[son];
            arr[son] = temp;
            //父和子节点变成下一个要比较的位置
            father = son;
            son = 2 * father + 1;
        }
    }
}

void HeapSort(int arr[],int len){
    int i;
    //初始化堆,从最后一个父节点开始
    for(i = len/2 - 1; i >= 0; --i){
        Heapify(arr,i,len);
    }
    //从堆中的取出最大的元素再调整堆
    for(i = len - 1;i > 0;--i){
        int temp = arr[i];
        arr[i] = arr[0];
        arr[0] = temp;
        //调整成堆
        Heapify(arr,0,i);
    }
}


异同:


归并排序:二归并排序 与 多路排序

4.7 二归并排序

定义:

归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

算法简述:

  1. 把长度为n的输入序列分成两个长度为n/2的子序列;
  2. 对这两个子序列分别采用归并排序;
  3. 将两个排序好的子序列合并成一个最终的排序序列。

动画演示:


代码实现:

C++版本

void MergeArray(int a[], int first, int mid, int last, int tmp[]){
	int i = first, n = mid;
	int j = mid + 1, m = last;
	int k = 0;
	while (i <= n&&j <= m)
	{
		if (a[i] < a[j])
			tmp[k++] = a[i++];
		else
			tmp[k++] = a[j++];
	}
	while (i <= n) {
		tmp[k++] = a[i++];
	}

	while (j <= m)
		tmp[k++] = a[j++];
	for (i = 0; i < k; i++)
		a[first + i] = tmp[i];
}


// 先递归地分解数列,再合并数列完成归并排序
void MergesortSection(int a[], int first, int last, int tmp[]){
	if (first < last)
	{
		int mid = (first + last) / 2;
		MergesortSection(a, first, mid, tmp);  // 左边有序
		MergesortSection(a, mid + 1, last, tmp); // 右边有序
		MergeArray(a, first, mid, last, tmp); // 合并两个有序序列
	}
}

void Mergesort(int a[], int n){
	int *p = new int[n];
	if (p == NULL)
		return;
	MergesortSection(a, 0, n - 1, p);
	delete[] p;
}

4.8 多路归并排序

定义:


代码实现:


异同:


四、 算法详情--线性时间非比较算法

4.9 基数排序

定义:

基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。

算法描述:

  1. 取得数组中的最大数,并取得位数;
  2. arr为原始数组,从最低位开始取每个位组成radix数组;
  3. 对radix进行计数排序(利用计数排序适用于小范围数的特点);

动画演示:




代码实现:

C++

//基数排序
//寻找数组中最大数的位数作为基数排序循环次数
int KeySize(int a[], int n){
    int key = 1;
    for(int i=0;i<n;i++){
        int temp = 1;
        int r = 10;
        while(a[i]/r>0){
            temp++;
            r*=10;
        }
        key = (temp>key)?temp:key;
    }
    return key;
}

//基数排序
void RadixSort(int a[], int n){
    int key = KeySize(a,n);
    int bucket[10][10]={0};
    int order[10]={0};
    for(int r = 1;key>0;key--,r*=10){
        for(int i=0;i<n;i++){
             int lsd = (a[i]/r)%10;
             bucket[lsd][order[lsd]++]=a[i];
        }

        int k = 0;
        for(int i = 0;i<10;i++){
            if(order[i]!=0){
                for(int j = 0;j<order[i];j++)
                    a[k++]=bucket[i][j];
            }
            order[i]=0;
        }
    }
}

异同:

4.10 桶排序

定义:

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。

算法描述:

    1. 设置一个定量的数组当作空桶;
    2. 遍历输入数据,并且把数据一个一个放到对应的桶里去;
    3. 对每个不是空的桶进行排序;
    4. 从不是空的桶里把排好序的数据拼接起来。 

动画演示:


代码实现:

C++版本

//桶排序 1,桶排序是稳定的 2,桶排序是常见排序里最快的一种,比快排还要快…大多数情况下 3,桶排序非常快,但是同时也非常耗空间,基本上是最耗空间的一种排序算法
void BucketSort(int a[],int n)
{
	int maxVal = a[0]; //假设最大为arr[0]
	for(int x = 1; x < n; x++)  //遍历比较,找到大的就赋值给maxVal
	{
		if(a[x] > maxVal)
			maxVal = a[x];
	}

	int tmpArrLen = maxVal + 1;
	int *tmpArr = new int[tmpArrLen];  //获得空桶大小
	int i, j;
	for( i = 0; i < tmpArrLen; i++)  //空桶初始化
		tmpArr[i] = 0;

	for(i = 0; i < n; i++)   //寻访序列,并且把项目一个一个放到对应的桶子去。
		tmpArr[ a[i] ]++;

	for(i = 0, j = 0; i < tmpArrLen; i++)
	{
		while( tmpArr[ i ] != 0) //对每个不是空的桶子进行排序。
		{
			a[j ] = i;  //从不是空的桶子里把项目再放回原来的序列中。i为索引,数组的索引位置就表示值
			j++;
			tmpArr[i]--;
		}
	}

}

异同:


4.11 计数排序

定义:

对于每个元素x,找出比x小的数的个数,从而确定x在排好序的数组中的位置。此算法需要辅助数组,是以空间换时间。

算法描述:

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

动画演示:


代码实现:

C++版本

void CountSort(vector<int> &arr, int maxVal) {
	int len = arr.size();
	if (len < 1)
		return;
	vector<int> count(maxVal+1, 0);
	vector<int> tmp(arr);
	for (auto x : arr)
		count[x]++;
	for (int i = 1; i <= maxVal; ++i)
		count[i] += count[i - 1];
	for (int i = len - 1; i >= 0; --i) {
		arr[count[tmp[i]] - 1] = tmp[i];
		count[tmp[i]]--;				//注意这里要减1
	}
}
int main()
{
	
	int i;
	int a[] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 10};
	
	// 计数排序 OK
	int max = a[0];
	for (int i = 1;i < 10; ++i)
	{
		if (a[i]>max)
		{
			max = a[i];
		}
	}
	Counting_sort(a,10,max+1);	

	for (int i = 0; i < 10; ++i)
	{
		cout<<a[i]<<" ";
	}
	system("pause");
	return 0;
}


1、基于比较的排序算法有:

(1)直接插入排序;
(2)冒泡排序;
(3)简单选择排序;
(4)希尔排序;
(5)快速排序;
(6)堆排序;
(7)归并排序。

2、基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部分资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。


Reference

1.https://www.cnblogs.com/onepixel/articles/7674659.html

2.http://www.ganecheng.tech/blog/52652705.html


版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/liuxiaohua23/article/details/80932836
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-02-13 15:38:07
  • 阅读 ( 1117 )
  • 分类:算法

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢