排序算法 大号 总结

排序算法种类繁多,我也是在算法的世界里晕头转向,今天,不能再当小迷糊了。。

目录

一.交换排序

冒泡排序

鸡尾酒排序

快速排序

二.插入排序

直接插入排序

链表插入排序

折半插入排序

希尔插入排序

三.选择排序

直接选择排序

锦标赛排序

堆排序

四.归并排序

归并排序

五.其他排序

桶排序

计数排序

基数排序


要想通俗易懂,举个例子就好

:将 8   6   1   5   4   3 按升序排好  1   3   4   5   6   8

那么就先从我们最熟悉的冒泡排序开始吧

一.交换排序

冒泡排序

平均时间复杂度:O(n^2) 。

原理:两两进行比较,前面的比后面的大的话,就交换

比较8和6:6   8   1   5   4   3

比较8和1:6   1   8   5   4   3

比较8和5:6   1   5   8   4   3

比较8和4:6   1   5   4   8   3

比较8和3:6   1   5   4   3   8

 

 

从中发现,6个数我们需要比较5次,这样比较完一趟后,并没有排好序,而只是将最大值8给排到了最后面,那么我们需要将8之前的数再排(8就不需要排了),每次都这样,共比较5趟就能排好了。

代码

#include<stdio.h>
int main()
{
	int a[6]={8,6,1,5,4,3};
	int i,j;
	for(i=0;i<5;i++)  //比较5趟 
	{
		for(j=0;j<5-i;j++) 
		{
			if(a[j+1]<a[j])
			{
				int t;
				t=a[j];
				a[j]=a[j+1];
				a[j+1]=t;
			}
		}
	}
	for(i=0;i<6;i++)
	{
	printf("%d ",a[i]);
    }
    return 0;
}

但是如果说对 1 2 3 4 6 5进行排序的话其实只进行一次就好,冒泡排序就有点浪费了,所以我们完全可以将冒泡排序给优化一下。

优化后的代码

#include<stdio.h>
int main()
{
	int a[6]={8,6,1,5,4,3};
	int i,j;
	for(i=0;i<5;i++)  //比较5趟 
	{
	    bool k=1; 
		for(j=0;j<5-i;j++) 
		{
			if(a[j+1]<a[j])
			{
				int t;
				t=a[j];
				a[j]=a[j+1];
				a[j+1]=t;
				k=0;
			}
		}
		if(k) break; 
	}
	for(i=0;i<6;i++)
	{
	printf("%d ",a[i]);
    }
    return 0;
}

鸡尾酒排序

如果说成是双向冒泡排序,大家应该是有点感觉了吧,无须怀疑,它的原理就是先找到最小的数字,把他放到第一位,然后找到最大的数字放到最后一位。然后再找到第二小的数字放到第二位,再找到第二大的数字放到倒数第二位。以此类推,直到完成排序。通俗点说就是一个是从左向右进行冒泡排序,另一个是从右向左进行冒泡排序,直到相遇。在我觉得,只要会了冒泡排序,鸡尾酒排序应该不成问题的,所以可以自己试一下,所以还以上面的数为例子,来把代码给写一下。
代码:

#include<stdio.h>
int main()
{
	int a[6]={8,6,1,5,4,3};
	int left=0,right=5;
	int i,j;
	while(left<right)
	{
		for(i=left;i<right;i++)  //从左向右 
		{
			if(a[i]>a[i+1])
			{
				int t=a[i];
				a[i]=a[i+1];
				a[i+1]=t;
			}
		}
		right--;
		for(j=right;j>left;j--)  //从右向左 
		{
			if(a[j]<a[j-1])
			{
				int t;
				t=a[j];
		 		a[j]=a[j-1];
				a[j-1]=t;
			}
		}
		left++;
	}
	for(i=0;i<6;i++)
	{
		printf("%d ",a[i]);
	}
	return 0;
}

快速排序

平均时间复杂度:O(nlogn) 。

我们从数组中选择一个元素,我们把这个元素称之为中轴元素,然后把数组中所有小于中轴元素的元素放在其左边,所有大于或等于中轴元素的元素放在其右边,显然,此时中轴元素所处的位置的是有序的。也就是说,我们无需再移动中轴元素的位置。
从中轴元素那里开始把大的数组切割成两个小的数组(两个数组都不包含中轴元素),接着我们通过递归的方式,让中轴元素左边的数组和右边的数组也重复同样的操作,直到数组的大小为1,此时每个元素都处于有序的位置。它的原理就是这样的,相信应该都能看懂,关键就是写代码了,很明显,要用到递归。

代码思想:

5 6 8 2 4 3 为例,对其进行排序:

以5作为中轴元素,设置 ij,分别从头开始往后找比5大的数和从尾开始往前找比5小的数

这里 j 找到了3(因为设置的中轴元素是最左边的数,所以先从后往前找),然后 i 找到了6,然后将这两个数交换位置,得:

5 3 8 2 4 6

然后继续找

j 找到了4,然后 i 找到了8,然后将这两个数交换位置,得:

5 3 4 2 8 6

再继续找

 j 找到了2,当 i 也到2时,此时 i=j,就结束了。

然后再让 i 位置的元素2与中轴元素换一换位置,得:
2 3 4 5 8 6

此时中轴元素5左边都是比它小的数,中轴元素右边都是比它大的数。

继续分别对它左边和右边的数重复以上的操作(不含中轴元素)

以右边的数为例:

以8作为中轴元素,i 指向8,j 指向6,分别开始往后找比8大的数和从尾开始往前找比8小的数

找到了6,当 i 也指向6时,此时 i=j,就结束了

然后再让 i 位置的元素6与中轴元素换一换位置,得:

6 8

此时右边就排好了。

代码:

#include<stdio.h>
int a[100],n;//定义全局变量 
void sort(int left,int right)
{
	if(left>right)//递归出口 
	return;
	int i=left;
	int j=right;
	int temp=a[left];//temp中存放中轴元素 
	while(i!=j)
	{
		//先从右边开始找 
		while(a[j]>=temp&&i<j)
		{
			j--;
		}
		while(a[i]<=temp&&i<j)
		{
			i++;
		}
		//找到a[i]和a[j],交换两个数在数组中的位置 
		if(i<j)
		{
			int t;
			t=a[j];
			a[j]=a[i];
			a[i]=t;
		}
	}
	//让中轴元素交换到中间位置 
	a[left]=a[i];
	a[i]=temp;
	sort(left,i-1);//继续递归中轴元素左边的数(不含中轴元素) 
	sort(i+1,right);//继续递归中轴元素右边的数(不含中轴元素) 
}
int main()
{
	//键盘输入 
	scanf("%d",&n);
	for(int k=0;k<n;k++)
	{
		scanf("%d",&a[k]);
	}
	//函数调用 
	sort(0,n-1);
	//输出,最后一个数之后没有空格 
	for(int i=0;i<n-1;i++)
	{
	printf("%d ",a[i]);
    }
    printf("%d",a[n-1]);
    return 0;
}

 

二.插入排序

直接插入排序

平均时间复杂度:O(n^2) 。

插入排序

它的原理其实是这样的:从第二个元素开始,让它与其前面的每一个元素进行比较,然后找到正确的位置。就比如说先从a[1]开始,让它与a[0]比较,如果a[0]大于a[1],就把a[0]的值赋给a[1]。再将最开始的a[1]赋给a[0],这样a[0]和a[1]就排好了,然后将a[2]分别与a[1],a[0]进行比较,若a[2]比他们都小的话,就把a[1]和a[0]统一向后移,将a[2]插到其前面,过程就是把a[1]赋给a[2],a[0]赋给a[1],最开始的a[2]赋给a[0]......

代码如下:

#include<stdio.h>
int main()
{
	int a[10]={8,9,1,7,2,3,5,4,6,0};
	int i,j;
	for(i=1;i<10;i++)
	{ 
		int t=a[i];   
		int k=i-1;  
		while(k>=0&&t<a[k])  //让t与其前面的每一个元素进行比较 
		{
		    k--;
		}
		for(j=i;j>k+1;j--)
		{
			a[j]=a[j-1];  //将前面的元素复制个后面的元素 
		}
			a[k+1]=t;  //将第i个元素插入到正确的位置 
	}
	for(i=0;i<10;i++)
    {
    	printf("%d ",a[i]);
	}
	return 0;
}

如果没看懂,那也没关系,给你举个例子,按上面的代码走一遍,就明白了,了解了上面的原理,我们也都知道:由于总是从第二个元素开始的,所以当我们进行到第i个的时候,其前面的i-1个元素都已排好序了。那么我们随便取个i=2(其前面的元素8和9已排好序),由于循环过程中a[2]的值会变,为了保留住最开始的a[2]的值,我们令t=a[2],t的值不会变,k=1以及下面的循环中的k>=0控制a[2]与前面的元素共比较2次,然后k=-1,用下面的循环控制赋值两次(即a[1]=a[2],a[0]=a[1]),此时a[1]=8,a[2]=9,最后再让最开始的a[2]的值赋给a[0],即a[0]=t,所以a[0]=1,这时a[0],a[1],a[2]就都排好序了。

以下代码是我改编的,可以看看:

#include<stdio.h>
int main()
{
	int a[6]={8,6,1,5,4,3};
	int i,j;
	for(i=0;i<6;i++)
	{
		if(a[i+1]<a[i])
		{
			for(j=i;j>=0&&a[j]>a[j+1];j--)
			{
				int t;
				t=a[j];
				a[j]=a[j+1];
				a[j+1]=t;
			}
		}
	}
	for(i=0;i<6;i++)
	{
		printf("%d ",a[i]);
	}
	return 0;
}

链表插入排序

即对链表进行直接插入排序。

代码:

#include<iostream>
using namespace std;
//定义 
typedef struct LNode{
	int data;
	struct LNode *next;
}LNode,*LinkList;
void InitList(LinkList &L);//链表的初始化 
void CreatList(LinkList &L,int n);//头插法创建链表 
void SortList(LinkList &L);//对链表进行排序 
void TravelList(LinkList &L);//遍历输出 
int main()
{
	LinkList L;
	//链表初始化 
	InitList(L); 
	//头插法创建链表
	int a;
	cin>>a;
	CreatList(L,a);
	//对链表进行排序 
	SortList(L); 
	//遍历输出排好序的链表 
	TravelList(L); 
} 
void InitList(LinkList &L)
{
	L=new LNode;
	L->next=NULL;
} 
void CreatList(LinkList &L,int n)
{
	for(int i=n;i>0;i--)
	{
		LinkList p;
		p=new LNode;
		cin>>p->data;
		p->next=L->next;
		L->next=p;
	}
}   
void SortList(LinkList &L)
{
	LinkList p,q,r;
	p=L->next->next;
	L->next->next=NULL;
	while(p)
	{
		q=p->next;
		r=L;
		while(r->next!=NULL&&r->next->data<p->data)
		{
			r=r->next;
		}
		p->next=r->next;
		r->next=p;
		p=q;
	}
}
void TravelList(LinkList &L)
{
	LinkList p;
	p=L->next;
	while(p!=NULL)
	{
		cout<<p->data<<" ";
		p=p->next;
	}
} 

运行一下:

8 5 6 2 10 4 7 1 9 3 对这10个数进行排序。

 

折半插入排序

折半插入排序(binary insertion sort)是对直接插入排序算法的一种改进,我们都知道,在直接插入排序算法当中,我们是通过传统的顺序查找第 i 个元素的位置,为了加快速度,我们可以通过折半查找来确定第 i 个元素的位置(就是所谓的折半查找),再将其插入。

原理

为了效果明显,我们举这样一个例子:1   3   4   5    6   2(第i个元素是2,其前面都已排好序)

取2前面值的中数是4,将2与4进行比较,2<4,再将2与4前面的数的中值1进行比较,2>1,像这样对半进行比较,具体的看代码。

代码如下:

#include<stdio.h>
int main()
{
	int a[6]={8,6,1,5,4,3};
	int i,j;
	for(i=1;i<6;i++)
	{ //折半查找应该插入的位置 
		int k=a[i];
		int left=0,right=i-1,mid;
		while(left<=right)
		{
			mid=(left+right)/2;
			if(a[i]>a[mid])
			{
				left=mid+1;  //将left的值更新 
			}
			else
			{
				right=mid-1;  //将right的值更新 
			}
		}
	//统一移动元素,然后将这个元素插入到正确的位置	
	for(j=i;j>left;j--)
	{
		a[j]=a[j-1];
	}
	a[left]=k;
    }
	for(i=0;i<6;i++)
	{
		printf("%d ",a[i]);
	}
	return 0;
}

希尔插入排序

平均时间复杂度:O(n^1.3) 。

直接插入排序在基本有序规模较小时使用高效,但是,对于较大规模并且无序的数据,就需要改进一下直接插入排序,这时我们就可以用希尔排序。

原理如图所示:

希尔排序

只是在外层多加了一个控制增量(gap)的循环罢了,图已经讲解的很清晰了,心里有了谱,就要试着去写一下代码了。

以下是我比葫芦画瓢写的代码,就是我只是把直接插入排序的间隔为1改成了间隔为gap,思想和代码是没有变化的,实际上,我这样是不对的,极其不规范,所以可千万别有和我一样偷懒的想法哦,哈哈……

代码

#include<stdio.h>
int main()
{
	int arr[10]={8,9,1,7,2,3,5,4,6,0};
	int length=10;
    int gap = length / 2; //在此增量取数组长度/2
	while(gap > 0)
    {
        for(int i = 0; i < gap; i++)  //共gap组数据,需要进行gap次直接插入排序,所以控制循环gap次 
        {
        	//对每组数据进行直接插入排序  
        	int g;
            for(g=gap;g<length;g+=gap)
            {
            int temp = arr[g];
            int k=g-gap;
            while((k>=0) && (temp<arr[k]))
            {
               k-=gap;
            }
            int h;
            for(h=g;h>k+gap;h-=gap)
            {
            	arr[h]=arr[h-gap];
			}
            arr[k+gap] = temp;
        }
    }
        gap /= 2;
    }
    for(int i=0;i<10;i++)
    {
    	printf("%d ",arr[i]);
	}
	return 0;
}

请注意:对各个分组进行插入的时候并不是先对一个组排序完了再来对另一个组排序,而是轮流对每个组进行排序,即这几个组的排序是同时进行的。接下来是时候展示一下真正的技术了,正确代码新鲜出炉喽!!!
正确代码演示:

#include<stdio.h>
#define N 100
int main()
{
	int a[N],n;
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		scanf("%d",&a[i]);
	}
	int gap;
	for(gap=n/2;gap>0;gap/=2)设置增量,每次都是gap/2,控制共需要分几次组 
	{
		for(int k=gap;k<n;k++)//依次对每一个数进行直接插入排序,即该数与其前面同组的数进行插入排序找到自己合适的位置 
		{
			int t=a[k];//用t来保存一下该数 
			int j=k-gap;
			while(t<a[j]&&j>=0)
			{
//				printf("%d ",k);
				a[j+gap]=a[j]; //t若比前面位置上的数字小,前面数就要后移
				j-=gap;//再继续往前进行比较,找自己的位置 
			}
			a[j+gap]=t;//找到了自己的位置,将保存该数的t插入这个位置
		}
	}
	for(int i=0;i<n;i++)
	{
		printf("%d ",a[i]);
	}
	return 0;
}

运行一下:

三.选择排序

直接选择排序

平均时间复杂度:O(n^2) 。

原理:

首先,找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置(如果第一个元素就是最小元素那么它就和自己交换)。其次,在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。如此往复,直到将整个数组排序。
8 6 1 5 4 3 为例演示一下:

找到最小值是 1,与第一个值8交换:1   6   8   5   4   3 

在除1以外的数中找到最小值是3,与第二个值6交换:1   3   8   5   4   6

......

......

......

就一直这样下去,直到全部排好。

代码

#include<stdio.h>
int main()
{
	int a[6]={8,6,1,5,4,3};
	int i,j;
	for(i=0;i<5;i++)
	{
	    int min=i;
		for(j=i+1;j<6;j++)
		{
			if(a[j]<a[min])
			min=j;
		}
		int t;
		t=a[i];
		a[i]=a[min];
		a[min]=t;
	}
	for(i=0;i<6;i++)
	{
		printf("%d ",a[i]);
	}
	return 0;
}

锦标赛排序

锦标赛排序是一种树形选择排序。

算法思想:

首先对n个元素进行两两比较,选出较小的那个,然后在较小的元素之间再进行两两比较,如此重复,直至选出最小的元素为止。
举例:

对该序列进行排序:

两两比较选出较小的,重复进行直到选出最小的为止 

 

将最小的值取出之后,用“∞”来代替该最小值元素在数中的位置,再重复上一步的操作,选出新的最小值取出来,一直重复此操作进行下去

缺点

1. 每次比较时与“∞”进行比较是多余的。
2. 辅助空间使用的多。

所以为了弥补这些缺点,1964年,堆排序产生了。 

堆排序

平均时间复杂度:O(nlogn) 。

由于对堆的不熟悉,首先需要普及一下堆的概念。

堆其实就是利用完全二叉树的结构来维护的一维数组,可以看作一个数组,也可以被看作一个完全二叉树。

按堆的特点可以把堆分为大顶堆和小顶堆。

大顶堆:每个结点的值都大于或等于其左右孩子结点的值。

小顶堆:每个结点的值都小于或等于其左右孩子结点的值。

以大顶堆为例:(在一维数组中的存储)

堆排序

如果只有一个结点,那它本身就是一个堆 。

目前我们需要解决的两个问题是:

一:怎样把一组无序的数建立成一个堆?

二:在取出堆顶的最大值或最小值后,怎样把剩下的元素又重新建立成一个堆?

先来解决第一个问题:

通过举例子来说明:

有这么一组数: 49 38 65 97 76 13 27 49  将其调整为一个小顶堆。

先建立一个初始的(无序的)完全二叉树:

所有叶子结点本身就是一个堆,因为其没有孩子。

所以我们从最后一个非叶子结点开始,往前依次进行调整

它的过程是这样的:

第一步:

从第4(即n/2=8/2=4)个元素97开始,将以该元素为根的二叉树调整为小顶堆,并修改数组中元素的位置

第二步:

 重复以上的操作,将以第3(即n/2-1=8/2-1=3)个元素13为根的二叉树调整为小顶堆,并修改数组中元素的位置

第三步:

 重复以上的操作,将以第2(即n/2-2=8/2-2=2)个元素38为根的二叉树调整为小顶堆,但发现38的左右两个孩子都比它大,所以不作调整。

 

第四步: 

重复以上的操作,将以第1(即n/2-1=8/2-1=3)个元素49为根的二叉树调整为小顶堆,并修改数组中元素的位置

到这里,还不算结束,只有把根节点调整到叶子结点才结束。

第五步:

 将以49为根结点的二叉树调整为一个小顶堆,并修改数组中元素的位置

这时一个小顶堆就建立好了。

接下来我们来解决第二个问题:

步骤(以小顶堆为例):

1.输出堆顶元素之后,以堆中最后一个元素替代之;

2.然后将根结点值与左、右子树的根结点值进行比较,并与其中小者进行交换。

3.重复上述操作,直至叶子结点,将得到新的堆,称这个从堆顶至叶子的调整过程为“筛选”。

 演示一下这个过程

以这个小顶堆为例:

第一步:

将堆顶元素(即最小值)取出,用堆中最后一个元素97代替。

第二步:

此时的堆顶元素97与其左右孩子进行比较,并与其较小的孩子27进行位置交换。

 

第三步:

重复第二步的操作,将97与其左右孩子进行比较,并与其较小的孩子49进行位置交换。

 

直到97变成叶子结点就结束了,又建立了一个新的小顶堆。

它的代码思想就是:

第一步:先将n个元素的无序序列,构建成大顶堆

第二步:将根节点与最后一个元素交换位置,(将最大元素"沉"到数组末端)

第三步:交换过后可能不再满足大顶堆的条件,所以需要将剩下的n-1个元素重新构建成大顶堆

第四步:重复第二步、第三步直到整个数组排序完成
 

大顶堆与小顶堆类似,下面用代码来演示一下大顶堆的排序过程

大顶堆排序代码:

#include<stdio.h> 
#define N 100
//将堆顶元素与堆的最后一个元素进行交换 
void swap(int *a,int *b)
{
	int t;
	t=*a;
	*a=*b;
	*b=t;
}
//建立大顶堆 
void HeapAdjust(int a[],int s,int m)
{
	int r=a[s];
	for(int j=2*s;j<=m;j*=2)//为了控制让堆顶元素与其左右孩子交换位置后继续与其下面的左右孩子进行比较交换,直到叶子结点 
	{
		if(j<m&&a[j]<a[j+1])
		{
			j++;
		}
		if(r>=a[j]) break;//根结点大于等于其左右孩子,无需进行交换 
		//进行交换位置 
		a[s]=a[j];
		s=j;
	}
	a[s]=r;
}
void HeapSort(int a[],int n)
{
	int i;
	for(int i=n/2;i>=1;i--)
	{
		HeapAdjust(a,i,n);//将一组数创建成一个大顶堆 
	}
	for(i=n;i>1;i--)
	{
		swap(&a[1],&a[i]);//将堆顶元素与堆的最后一个元素进行交换 
		HeapAdjust(a,1,i-1);//将剩下的元素重新建立一个大顶堆 
	}
}
int main()
{
	int n;
	int a[N]={0};
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	//函数调用 
	HeapSort(a,n); 
	for(int k=1;k<=n;k++)
	{
		printf("%d ",a[k]);
	}
	return 0;
}

为确保代码的正确性,输30个数来运行一下: 

四.归并排序

归并排序

平均时间复杂度:O(nlogn) 。

速度仅次于快速排序,为稳定排序算法一般用于对总体无序,但是各子项相对有序的序列。

排序思想(采用分治法)

以这个长度为5的数组为例:3 2 5 1 4 

在处理数组排序时,如果数组中只有 1 个元素,那么该数组本身就可看作是排好序的数组。

首先将它拆分到不能拆为止,即每一个元素都是一个数组。

这样的数组已经是“排好序”的数组了,现在需要做的就是把这些已经排好序的子数组合并。

合并的过程其实就是:分别从两个数组中的首元素开始进行比较 ,把较小的那个拿出来放入我们已经定义好的数组中,然后再移动指针到下一个元素继续进行比较,再重复上述的过程,最终将已经排好序的两个数组合并为有序的一个数组。

总的过程就是这样的:

 

代码

 

#include<stdio.h>
#define N 100 
//合并 
int merge(int a[],int start,int mid,int end)
{
	int s1=mid-start+1;
	int s2=end-mid;
	int left[s1],right[s2];//定义left和right数组 
    //我们先将要合并的拆分后的两个子数组分别保存在 left 和 right 数组里
	for(int i=0;i<s1;i++)
	{
		left[i]=a[start+i];
	}
	for(int j=0;j<s2;j++)
	{
		right[j]=a[mid+1+j];
	}
	//接着,我们逐个比较 left 和 right 中的元素,按照顺序填入 a数组中 
	int i=0,j=0,k=0;
	for(k=start;i<s1&&j<s2;k++)
	{
		if(left[i]<right[j])
		{
			a[k]=left[i];
			i++;
		}
		else
		{
			a[k]=right[j];
			j++;
		}
	}
	//当一个数组中的元素取完时(该数组为空时) ,将另一个数组中剩余的元素复制到 a数组中 
	if(i<s1)
	{
		while(i<s1)
		{
			a[k]=left[i];
			k++;
			i++;
		}
	}
	if(j<s2)
	{
		while(j<s2)
		{
			a[k]=right[j];
			k++;
			j++;
		}
	}
}
//拆分 
void divide(int a[],int start,int end)
{
	if(start>=end)
	return;
	int mid=(start+end)/2;
	//递归拆分 
	divide(a,start,mid); 
	divide(a,mid+1,end);
	merge(a,start,mid,end);
}
int main()
{
	int a[N],n;
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		scanf("%d",&a[i]);
	}
	//函数调用 
	divide(a,0,n-1);
	//输出排好序后的序列 
	for(int i=0;i<n;i++)
	{
		printf("%d ",a[i]);
	}
	return 0;
} 

运行:

五.其他排序

桶排序

算法思想

以这个为例:

首先遍历整个数组,求出来它的最小值0.0,以及最大值1.0 。

然后计算它们的差值是1,图中我们有4个桶,(当然也可以弄5个、10个桶),那么每个桶的范围就是1/4=0.25

如第一个桶的范围是0.0到0.25,第二个桶的范围是0.25到0.5,第一个桶的范围是0.5到0.75,第一个桶的范围是0.75到1.0,

然后遍历数组中的每一个元素,看它属于哪个范围,然后把它放进相应的桶里

然后在每一个桶里再进行单独的排序

最后依次输出每一个排好序的桶中的元素。

 产生的问题

1. 每一个桶内还要再进行单独的排序

2. 桶要用什么样的数据结构去存储。

如果用数组的话,数组应该定义多长,长度不好定(因为它存储的只是一个范围里的元素,我们也不知道有多少个元素在这个范围内),假设原数组中所有的元素都在一个范围内,都在一个桶里,考虑到这种情况,那我们定义的桶数组就都得跟原数组一样长。

如果用链表的话,它的排序比较麻烦。

所以实际上普通的桶排序用的不多,用的最多的就是它的特殊的例子:计数排序(最常用的)和基数排序。

计数排序

看不懂没关系,先看下面的详解:

1 4 1 2 7 5 2 进行排序 :

首先先把该无序的序列存到一个一维数组中,之后我们创建一个新的数组用来存放每一个元素出现的次数。

 

这是已经全部存到了新数组中的结果。

具体是这样存的:我们来看新数组,下标为0(即代表在原无序序列中元素值为0的元素)的没有1个,即0个,所以里面存的0。下标为1(即代表在原无序序列中元素值为1的元素)的有2个,所以里面存的是2。这个思想就是无序序列中的元素的元素值对应的是新数组中的下标,比如无序序列中的元素1有2个,所以新数组下标为1的里面存的就是2。

再创建一个与原无序序列数组长度相同的数组用来存放排好序后的序列。

这是最终的结果。 

到这里先不要懵 ,它的过程是这样的:

首先我们需要先将index数组给变一下

变为

 

是这样变的:从下标为1开始,每一次该下标中的元素值与其前面的所有元素值之和,然后将相加的结果存到该下标中,如改变后的下标为1中的2是由改变前的2+0所得, 改变后的下标为2中的4是由改变前的2(下标为2里边存的)+2(下标为1里边存的)+0(下标为0里边存的)所得。

即(以下标2为例):

之所以这样加,是因为此刻每个下标里存的值都已经为对应该下标的元素的前面有几个元素,就比如说下标为2(即对应无序序列中元素值为2的元素)里存放的是4,代表它排好序后的位置应该是第4个,因为我们这个新数组本来存的就是下标为0的(即对应无序序列中元素值为0的元素)为0个,下标为1的(即对应无序序列中元素值为1的元素)为2个,下标为2的(即对应无序序列中元素值为2的元素)为2个,所以加起来就是4个元素。

改变好 index 数组之后:

 

places数组里是这样存的:以为 index 数组的下标就代表原无序序列中元素的值,而 index 数组中存的值代表该下标元素排好序后的位置。如无序序列中第一个元素1在places数组中的位置为2,这时 index数组 中下标为1里面存的2要减1变为1,这是因为当遍历到无序序列中第3个元素1时,这时它所对应的位置就不是2了,而是1。

代码

#include<stdio.h>
#define N 100 
int main()
{
	int a[N]={0},n;
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		scanf("%d",&a[i]);
	}
	//求出最大最小值 
	int max=a[0],min=a[0];
	for(int k=1;k<n;k++)
	{
		if(a[k]>max)
		{
			max=a[k];
		}
		if(a[k]<min)
		{
			min=a[k];
		}
	}
	int index[max-min+1+1]={0};//长度为max-min+1,再加1 
	//将原数组中的数对应到新计数数组中 
	for(int i=0;i<n;i++)
	{
		index[a[i]-min+1]++;
	}
	//计数数组变形,数组中新元素的值是前面元素累加之和的值 
	for(int j=1;j<max-min+2;j++)
	{
		index[j]+=index[j-1];
	}
	//创建结果数组,用于保存排好序的序列 
	int places[N]={0};
	for(int k=0;k<n;k++)
	{
		places[index[a[k]-min+1]-1]=a[k];//将计数数组中的值填充到结果数组中 
		index[a[k]-min+1]--;//如果后面遇到了相同的元素,在前面元素的基础上排 
	}	
	//输出保存有序序列的结果数组 
	for(int i=0;i<n;i++)
	{
		printf("%d ",places[i]);
	}
	return 0;
}

运行:

基数排序

平均时间复杂度:O(d(n+r)) 。

又称“桶子排序”,属于稳定性的排序。

算法思想

分别按它的每一位数(个位、十位、百位……)进行排序

以LSD为例:对 73 22 93 43 55 14 28 65 39 81 进行排序

首先根据个位数的数值,在走访数值时将它们分配至编号0到9的桶子中:
0
81
22
73 93 43
14
55 65
6
7
28
39
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
81, 22, 73, 93, 43, 14, 55, 65, 28, 39

接着再进行一次分配,这次是根据十位数来分配:
0
14
22 28
39
43
55
65
73
81
93
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
14, 22, 28, 39, 43, 55, 65, 73, 81, 93
这时候整个数列已经排序完毕;如果排序的对象有三位数以上,则持续进行以上的动作直至最高位数为止。

代码

#include<stdio.h>
#include<math.h>
#define N 100 
void sort(int a[],int intn,int loop)
{
	//对每一位(个位、十位、百位……)进行桶分配 
	int buckets[10][20]={};
//	以123为例 
//	个位桶 : (123/1)%10=3 
//	十位桶 : (123/10)%10=2 
//	百位桶 : (123/100)%10=1
    int Num=(int)pow(10,loop-1);//Num即为上式中的1、10、100 
    int index;
	for(int i=0;i<intn;i++)
	{
		index=(a[i]/Num)%10;
		for(int j=0;j<20;j++)
		if(buckets[index][j]==NULL)//如果该位置还没存数,就存入,反之,依次往后存 
		{
			buckets[index][j]=a[i];
			break;
		}
	}
	//将桶中的数,倒回到原有数组中 
	int k=0;
	for(int i=0;i<10;i++)
	{
		for(int j=0;j<20;j++)
		{
			if(buckets[i][j]!=NULL)//如果桶中该位置有数,就把它拿出来依次放入到原有数组中 
			{
				a[k]=buckets[i][j];
				buckets[i][j]=NULL;
				k++;
			}
		}
	} 
}
int main()
{
	int a[N],n;
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		scanf("%d",&a[i]);
	}
	//求出最大值 
	int max=a[0];
	for(int k=1;k<n;k++)
	{
		if(a[k]>max)
		{
			max=a[k];
		}
	}
	//求出最大位数 
	int count=0;
	while(max!=0)
	{
		max=max/10;
		count++;
	}
	//基数排序 
	for(int i=1;i<=count;i++)
	{
		sort(a,n,i);
	} 
	//输出排好序后的序列 
	for(int i=0;i<n;i++)
	{
		printf("%d ",a[i]);
	}
	return 0;
} 

运行:

LSD的基数排序适用于位数小的数列,如果位数多的话,使用MSD的效率会比较好。MSD的方式与LSD相反,是由高位数为基底开始进行分配,但在分配之后并不马上合并回一个数组中,而是在每个“桶子”中建立“子桶”,将每个桶子中的数值按照下一数位的值分配到“子桶”中。在进行完最低位数的分配后再合并回单一的数组中。

总结:

数据量比较大的情况下选择堆排序较好

数据基本有序的情况下选择插入排序较好

在随机的情况下选择快速排序较好

选择排序元素的比较次数与元素的初始排序无关
 

我记得有一位大神曾说,一定要先自己写,不要看示例代码!不要看示例代码!不要看示例代码!重复了三遍,也可见其重要性了哈哈。

 额,血量不足了……

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值