zoukankan      html  css  js  c++  java
  • 浅谈排序算法[动图]

    本文只是介绍多种排序思想及代码,具体实现细节将不会提及

    内部排序

    内部排序主要是在OI中可以使用,外部排序则不能。
    下面我们将讲述(21)种内部排序,请做好准备。

    1.内省式排序

    其实是多种排序算法的集合,先进行快速排序,如果序列接近有序或比较小用插入排序,如果递归层数过多则改用堆排序。

    你有没有想:一开始就介绍这么多种排序算法的集合,还没学那些排序呢!

    不用担心,这个东西的实现代码最简单:

    sort(arr,arr+n);
    

    如果想要逆序排序或者其他规则排序:

    #include<algorithm>     //一定要用这个头文件
    bool cmp(obj a,obj b)   //obj指你要排序对象的类型
    {
        ...                 //里面的规则如果写return a<b;就是从小到大啦,sort默认从小到大。
        return ...;
    }
    sort(arr,arr+n,cmp);
    

    2.冒泡排序

    冒泡就是先遍历数组1~n-1的元素,如果a[i]>a[i+1]就把他们交换,然后遍历数组2~n-1的元素(dots)最后遍历到n-1~n-1结束。

    动图如下:


    效果图(可视化感觉更强):

    代码如下:

    template<typename T>
    void BubbleSort(T arr[], T n)
    {
        for (int i=0;i<n-1;i++)
            for (int j=0;j<n-i-1;j++)
                if (arr[j]>arr[j+1])
                {
                    T temp=arr[j];
                    arr[j]=arr[j+1];
                    arr[j+1]=temp;
                }
    }
    

    还可以加个优化,如果没有交换发生(以及有序)就不用再遍历了:

    template<typename T>
    void BubbleSort(T arr[], T n)
    {
        for (int i=0;i<n-1;i++)
            for (int j=0;j<n-i-1;j++)
            {
                bool flag=false;
                if (arr[j]>arr[j+1])
                {
                    T temp=arr[j];
                    arr[j]=arr[j+1];
                    arr[j+1]=temp;
                    flag=true;
                }
                if (!flag) return ;
            }
    }
    

    时间复杂度(mathcal{O}(n^2))

    3.[冒泡排序优化]鸡尾酒排序

    鸡尾酒排序,也就是定向冒泡排序鸡尾酒搅拌排序搅拌排序(也可以视作选择排序的一种变形),涟漪排序来回排序快乐小时排序,是冒泡排序的一种变形。

    这个算法不是单纯的单向排序了,而是第一次从左到右冒泡,第二次从右到左冒泡(dots)

    这样的话会优化常数,但还是(mathcal{O}(n^2))

    动图:

    代码如下:

    void Cocktailsort(int s[],int n)
    {
        int left=0;
        int right=n-1;
        while(left<right)
       {
            for(int i=left;i<right;i++)
                if (s[i]>s[i+1]) swap(s[i],s[i+1]);
            right--;
            for(int i=right;i>left;i--)
                if(s[i]<s[i-1]) swap(s[i],s[i-1]);
            left++;
        }
    }
    

    鸡尾酒排序也可以加优化:判断是否产生交换,代码并不难写,读者可以自己实现。

    4.[冒泡排序优化]地精排序

    被Dick Grune称为最简单的排序算法。整个算法只有一层循环,默认情况下前进冒泡,一旦遇到冒泡的情况发生就往回冒,直到把这个数字放好,然后继续前进,前进到数组最后一个数结束。此排序算法虽然代码极短,但效率不高。

    动图:

    如果看不了来这里

    代码:

    template<typename T>
    void GnomeSort(T *a,T n)
    {
    	int i=0; 
    	while (i<n)
    	{
    		if (i==0||a[i-1]<=a[i]) i++;
    		else swap(a[i],a[i-1]),i--;
    	}
    }
    

    emm也是(mathcal{O}(n^2))

    5.[冒泡排序逆优化]臭皮匠排序

    臭皮匠排序(Stooge Sort),是一种低效的排序算法,在《算法导论》第二版第7章的思考题中被提到,是由Howard Fine等教授提出的所谓“漂亮的”排序算法。将数列平分为三个子串,依次递归排序前两个子串、后两个子串、前两个子串,最后确保整个数列有序。此算法在最坏情况下的递归式为(T(n) = 3T(2n/3) + 1)。由主定理很容易知道它的算法复杂性为:(T(n) = mathcal{O}(n^{log_frac{3}{2}, 3}))。很显然(log_frac{3}{2}, 3>2),也就是说这个算法比插入排序的(mathcal{O}(n^2))性能还差。

    最大就(mathcal{O}(n^{2.7}))吧。。

    动图:

    代码如下:

    void StoogeSort(int *a,int i,int j)
    {
    	if (a[i]>a[j]) swap(a[i],a[j]);
    	if ((i+1)>=j) return ;
    	int k=(j-i+1)/3;
    	StoogeSort(a,i,j-k);
    	StoogeSort(a,i+k,j);
    	StoogeSort(a,i,j-k);
    }
    

    6.[冒泡排序优化]奇偶排序

    你有没有想冒泡排序优化怎么这么多

    奇偶排序(OddEven Sort),是一种相对简单的排序算法,最初发明用于有本地互联的并行计算。此算法通过比较数组中相邻的(奇-偶)位置数字对,如果该奇偶对是错误的顺序(第一个大于第二个),则交换。下一步重复该操作,但针对所有的(偶-奇)位置数字对。如此交替下去,直到不发生交换,则排序结束。
    在并行计算排序中,使用该算法,每个处理器对应处理一个值,并仅有与左右邻居的本地互连。所有处理器可同时与邻居进行比较、交换操作,交替以奇-偶、偶-奇的顺序。该算法由Habermann在(1972)年最初发表并展现了在并行处理上的效率。但在单处理器串行运行此算法,类似冒泡排序,较为简单但效率并不特别高。

    示意图:

    最差(mathcal{O}(n^2))

    代码:

    void OddEvenSort(int *a, int len)
    {
    	bool swapped=true;
    	while (swapped)
    	{
    		swapped=false;
    		for (int i=0;i<len-1;i=i+2)
    			if (a[i]>a[i+1])
    			{
    				swap(a[i],a[i+1]);
    				swapped=true;
    			}
    		for (int i=1;i<len-1;i=i+2)
    			if (a[i]>a[i+1])
    			{
    				swap(a[i],a[i+1]);
    				swapped=true;
    			}
    	}
    }
    

    冒泡排序优化当然都是稳定排序啦。

    7.选择排序

    每次选出一个最小的放到左边,然后依次这么做就行。

    时间复杂度(mathcal{O}(n^2))

    动图:

    效果图(可视化感觉更强):

    选择排序的在各种初始条件下的排序效果如下:

    代码如下:

    template<typename T>
    void ssort(T* array,int size)
    {
        for(int i=0;i<size;i++)
        {
            T Min=array[i];
            int k=i;
            for (int j=i+1;j<size;j++)
                if (array[j]<Min) min=array[j],k=j;
            if (k!=i) array[k]=array[i],array[i]=Min;
        }
    }
    

    你也可以一次确定一个(max)和一个(min),代码不难的。

    8.[选择排序优化]堆排序

    每次取最小值用堆实现,这样就更快了,可以达到(mathcal{O}(nlog n))

    动图:

    代码:

    //STL大法好!
    template<typename T>
    void heapsort(T* a,int n);
    {
        priority_queue<T> Q;
        for (int i=0;i<n;i++) Q.push(a[i]);
        for (int i=0;i<n;i++)
        {
            T Top=Q.top();
            Q.pop();
            a[i]=Top;
        }
    }
    

    9.插入排序

    从左边设一个有序区,每次从右边插入有序区一个元素,最后就能排序。

    时间复杂度(mathcal{O}(n^2))

    动图:

    效果图(可视化感觉更强):

    插入排序的在各种初始条件下的排序效果如下:

    代码如下:

    void Insert_Sort(int* arr,int n)
    {
    	for (int i=0;i<n;i++)
    		for (int j=i;j>0;j--)
    			if (arr[j]<arr[j-1])
    				swap(arr[j],arr[j - 1]);
    }
    

    10.[插入排序优化]图书馆排序

    Library Sort基于折半查找的插入排序,插入时在元素附近空出一定位置,这样推入后移动元素的复杂度由原来的(O(n))下降为平均(O(1)),于是整个算法的复杂度达到(mathcal{O}(nlog n))。当输入正序或倒序时,插入点都在同一位置,“留空位”的策略失效,这时就出现最坏复杂度(mathcal{O}(n^2))

    动图:

    由于太冷门所以没有动图

    代码:

    void librarySort(int length,float factor,int elements[])
    {
    	int i,j;
    	int expandedLen = (int)((1+factor)*length);
    	int* orderedElem = (int*) malloc(expandedLen*sizeof(int));
    	int flag = 1<<31;
    	for(i=0; i<expandedLen; i++)
    		orderedElem[i] = flag;
    	int index = 1;
    	int numOfIntercalatedElem = 1;
    	orderedElem[0] = elements[0];
    	while(length>numOfIntercalatedElem)
    	{
    		for(j=0; j<numOfIntercalatedElem; j++)
    		{
    			int mid;
    			int low = 0;
    			int high = 2 * numOfIntercalatedElem - 1;
    			while(low <= high)
    			{
    				mid = (low + high)/2;
    				int savedMid = mid;
    				while(orderedElem[mid] == flag)
    				{
    					if(mid == high)
    					{
    						mid = savedMid - 1;
    						while(orderedElem[mid] == flag)
    							mid--;
    						break;
    					}
    					mid++;
    				}
    				if(elements[index] > orderedElem[mid])
    				{
    					low = mid + 1;
    					while(orderedElem[low] == flag)
    						low = low+1;
    				}
    				else high = mid - 1;
    			}
    			if(orderedElem[high+1] == flag)
    				orderedElem[high+1] = elements[index];
    			else
    			{
    				int temp = high+1;
    				while(orderedElem[temp] != flag)
    				{
    					temp--;
    					if(temp < 0)
    					{
    						temp = high+1;
    						break;
    					}
    				}
    				while(orderedElem[temp] !=flag)
    				{
    					temp++;
    				}
    				while(temp < high)
    				{
    					orderedElem[temp] = orderedElem[temp+1];
    					temp++;
    				}
    				while(temp > high+1)
    				{
    					orderedElem[temp] = orderedElem[temp-1];
    					temp--;
    				}
    				orderedElem[temp] = elements[index];
    			}
    			index++;
    			if(index == length)
    				break;
    		}
    		numOfIntercalatedElem *=2;
    		int generatedIndex;
    		for(j=numOfIntercalatedElem; j>0; j--)
    		{
    			if(orderedElem[j] == flag)
    				continue;
    			generatedIndex = j*2;
    			if(generatedIndex >= expandedLen)
    			{
    				generatedIndex = expandedLen - 1;
    				if(orderedElem[generatedIndex] != flag)
    					break;
    			}
    			orderedElem[generatedIndex] = orderedElem[j];
    			orderedElem[j] = flag;
    		}
    	}
    	for(i=0; i<expandedLen; i++)
    		printf("%d
    ",orderedElem[i]);
    }
    

    11.[插入排序优化]希尔排序

    第一次把序列(k_0)的倍数分组,每组进行插入排序,

    第二次把序列(k_1)的倍数分组,每组进行插入排序,

    (dots)

    最终,总会排好序。

    (k_0>k_1>dots>k_n,k_n=1)

    动图:

    希尔排序在不同数据下的效果:

    其中,增量序列(k)的取值,目前没有找出最好的。

    代码:

    void ShellSort(int arr[],int length)
    {
        if (arr==NULL||length<=0) return;
        int increment=length;
        while (increment>1)
        {
            increment=increment/3+1;
            for (int i=increment;i<length;i++)
            {
                int temp=arr[i];
                if (arr[i]<arr[i-increment])
                {
                    int j;
                    for (j=i-increment;j>=0&&arr[j]>temp;j=j-increment)
                        arr[j+increment]=arr[j];
                    arr[ +increment]=temp;
                }
            }
        }
    }
    

    时间复杂度是(mathcal{O}(n^{1.7}))(mathcal{O}(n^2))

    12.快速排序

    每次取一个基准数,把比他小的都放在左边,比他大的都放在右边,然后递归排序左边和右边。

    动图:

    效果图(非常震撼!):

    快速排序的在各种初始条件下的排序效果如下:

    代码如下:

    template<typename T>
    void qsort(T* a,int left,int right)
    {
    	int i,j,t,temp;
    	if (left>right) return;
        temp=a[left];
        i=left;
        j=right;
        while(i!=j)
    	{
        	while (a[j]>=temp&&i<j) j--;
        	while (a[i]<=temp&&i<j) i++;       
        	if (i<j) t=a[i],a[i]=a[j],a[j]=t,
        }
        a[left]=a[i];
        a[i]=temp;
        qsort(left,i-1);
        qsort(i+1,right);
    }
    

    时间复杂度平均(mathcal{O}(nlog n))

    13.归并排序

    就是把他分成两半,然后递归左半边和右半边,然后重新合并起来。

    图片:

    效果图:

    归并排序的在各种初始条件下的排序效果如下:

    代码:

    void Merge(int arr[],int l,int q,int r)
    {
        int n=r-l+1;
        int* tmp=new int[n];
        int i=0;
        int left=l;
        int right=q+1;
        while (left<=q&&right<=r)
            tmp[i++]=arr[left]<=arr[right]?arr[left++]:arr[right++];
        while (left<=q) tmp[i++]=arr[left++];
        while (right<=r) tmp[i++]=arr[right++];
        for (int j=0;j<n;++j) arr[l+j]=tmp[j];
        delete[] tmp;
    }
    void MergeSort(int arr[],int l,int r)
    {
        if (l==r) return;
        int q=(l+r)/2;
        MergeSort(arr,l,q);
        MergeSort(arr,q+1,r);
        Merge(arr,l,q,r);
        
    }
    

    时间复杂度平均(mathcal{O}(nlog n))

    14.计数排序

    用一个数组kk[i]表示原序列中i出现的次数。

    遍历一遍数组就能排序。

    动图:

    代码很好写,就不写了。

    15.[计数排序优化]鸽巢排序

    就是把每个数字减去最小值在进行计数排序,节约空间。

    代码并不难写,就不写了。

    16.桶排序

    计数排序不能应对负数和小数,怎么办?

    桶排序:开(n)个桶,每个桶保存(i)(j)之间的数,每次把序列中数字分配到桶中,再将桶内部进行排序,最后输出。

    其实可以分配时按照插入排序思路插入的,时间复杂度(mathcal{O}(n))

    代码:

    void BucketSort(int *arr, int n)
    {
        int max = arr[0];
        for (int i = 1; i < n; i++)
        {
            if (arr[i] > max)
                max = arr[i];
        }
        vector<int> *bucket = new vector<int>[max / 10 + 1];
        for (int i = 0; i < n; i++)
        {
            int change = arr[i] / 10;
            bucket[change].push_back(arr[i]);
        }
        for (int i = 0; i < n; i++)
            if (bucket[i].size() > 0)
                sort(bucket[i].begin(), bucket[i].end());
        int index = 0;
        for (int i = 0; i < n; i++)
            for (int j = 0; j < bucket[i].size(); j++)
                arr[index++] = bucket[i][j];
    }
    

    17.基数排序

    按照数位每次进行多关键字排序,最后就能排好序。

    动图:

    代码:

    bool rxsort(int A[],int l,int h,int d,int k)
    {
        if (NULL==A||l>h)
            return false;
        int size = h-l+1;
        int* counts = new int[k];
        int* temp = new int[size];
        int index;
        int pval=1;
        for (int i=0;i<d;i++)
    	{
            for (int j=0;j<k;j++)
                counts[j] = 0;
            for (int j=l;j<=h;j++){index = (int)(A[j]/pval)%k;counts[index]++;}
            for (int j=1;j<k;j++)
                counts[j] = counts[j] + counts[j-1];
            for (int j=h;j>=l;j--)
    		{
                index = (int)(A[j]/pval)%k;
                temp[counts[index]-1] = A[j];
                counts[index]--;
            }
            for (int j=0;j<size;j++)
                A[j+l] = temp[j];
            pval = pval*k;
        }
        delete[] counts;
        delete[] temp;
    }
    

    时间复杂度(mathcal{O}(d(n+k)))

    18.[排序界的一朵奇葩]睡眠排序

    睡眠排序的主要逻辑是构造(n)个线程,它们和这(n)个数一一对应。初始化后,线程们开始睡眠,等到对应的那么多个时间单位后各自醒来,然后输出对应的数。这样最小的数对应的线程最早醒来,这个数最早被输出。等所有线程都醒来,排序就结束了。

    由于太奇葩,所有动图和代码都没有

    19.[排序界的一朵奇葩]猴子排序

    每次随机打乱,直到排好序。

    由于太奇葩,所有动图和代码都没有

    20.[排序界的一朵奇葩]珠排序

    我们知道,在重力的作用下,如果将算盘立起来,无论怎么拨动,算珠最终都会掉下来紧挨在一起,那如果每根柱子上的算珠个数不太一样呢?

    见图片:

    如图,我们看到神奇的重力已经帮我们排好序了。。。。

    这个排序算法是时间复杂度最低的!(mathcal{O}(1))!!!

    由于太奇葩,所有动图和代码都没有

    21.[排序界的一朵奇葩]面条排序

    如果桌子上有一把长短不一的面条,此时你将面条立起来,下端平放在桌面上,此时你用手掌在空中从上往下缓慢移动,慢慢的,你的手掌触碰到了一根面条(这根面条是最高的),你将这根面条抽走(抽走的面条当然想怎么吃就怎么吃),手继续慢慢向下移动,这是,你又碰到了倒数第二高的面条,你又将其抽走(dots)

    然后吃到的面条序列就是排序后数组。

    由于太奇葩,所有动图和代码都没有

  • 相关阅读:
    遗传算法
    UVa 11584 Partitioning by Palindromes
    UVa1625 Color Length
    UVa10003 Cutting Sticks
    UVa1347 Tour
    UVa116 (单向TSP,多决策问题)
    uVa 12563 Jin Ge Jin Qu
    模糊综合评判
    Python进阶(5)_进程与线程之协程、I/O模型
    Python进阶(4)_进程与线程 (python并发编程之多进程)
  • 原文地址:https://www.cnblogs.com/CDOI-24374/p/12300350.html
Copyright © 2011-2022 走看看