代码改变世界

排序算法

2016-04-17 14:00  koujiao的蒻苣  阅读(213)  评论(0编辑  收藏  举报

排序方法 平均情况           最好情况     最坏情况     辅助空间 稳定性
冒泡排序 O(n^2)             O(n)        O(n^2)    O(1)     稳定
选择排序 O(n^2)             O(n^2)      O(n^2)    O(1)        不稳定
插入排序 O(n^2)            O(n)       O(n^2)    O(1)         稳定
希尔排序O(n*log(n))~O(n^2)     O(n^1.3)      O(n^2)   O(1)       不稳定
堆排序 O(n*log(n))           O(n*log(n))    O(n*log(n))  O(1)       不稳定
归并排序 O(n*log(n))         O(n*log(n))   O(n*log(n))   O(n)       稳定
快速排序 O(n*log(n))          O(n*log(n))   O(n^2)    O(logn)   不稳定

冒泡排序经过优化以后,最好时间复杂度可以达到O(n)。设置一个标志位,如果有一趟比较中没有发生任何交换,
可提前结束,因此在正序情况下,时间复杂度为O(n)。选择排序在最坏和最好情况下,都必须在剩余的序列中选择最小(大)的数,
与已排好序的序列后一个位置元素做交换,依次最好和最坏时间复杂度均为O(n^2)。插入排序是在把已排好序的序列的后一个元素插入到前面已排好序
(需要选择合适的位置)的序列中,在正序情况下时间复杂度为O(n)。堆是完全二叉树,因此树的深度一定是log(n)+1,最好和最坏时间复杂度均为O(n*log(n))。
归并排序是将大数组分为两个小数组,依次递归,相当于二叉树,深度为log(n)+1,因此最好和最坏时间复杂度都是O(n*log(n))。快速排序在正序或逆序情况下
,每次划分只得到比上一次划分少一个记录的子序列,用递归树画出来,是一棵斜树,此时需要n-1次递归,且第i次划分要经过n-i次关键字比较才能找到第i个记录,
因此时间复杂度是\sum_{i=1}^{n-1}(n-i)=n(n-1)/2,即O(n^2)。

冒泡优化:

问题分析:冒泡排序算法的基本思想就是相邻数据比较若逆序则交换,经过n-1趟比较交换后,逐渐将小数据冒到数组的前部,大的数据则陈到数组的后部,从而完成排序工作。

现在假设本来的数据本来就是从小到大有序的,则原有算法仍要做n-1趟比较操作,事实上,一趟比较下来,若发现没有进行过交换,若发现没有进行过交换,就已经说明数据全部有序,无须进行其后的比较操作了。

当然年数据原本有序的概率,但经过少于n-1趟比较交换操作后,数据就已经有序的概率却非常高。因此,为提高效率可以对冒泡排序算法进行改进,当发现某趟没有交换后就停止下一趟的比较操作。

人类可以用眼睛“宏观”地发现一组数据是否有序的状态,但算法值能通过逐一比较才能明确数据是否处于有序状态,这用逻辑表达式是不可能实现的,这时就考虑用标志量来记录每趟交换数据的的情况,如flag=0表示没有进行过交换,一旦有数据进行交换则置flag为1,表示已进行过交换,当一趟比较交换完成后,若flag仍为0时,则无须进行下一趟操作,否则若flag为1时,只能继续进行下一趟操作。

#include<stdio.h>
int main()
{
 int i,j,t,n,a[100],flag;
 while(scanf("%d",&n)!=EOF)
 {
  for(i=0;i<n;i++)
   scanf("%d",&a[i]);
  flag=1;
  for(i=1;i<n&&flag==1;i++)
  {
   flag=0;
   for(j=n-1;j>=i;j--)
   {
    if(a[j]<a[j-1])
    {
     t=a[j];
     a[j]=a[j-1];
     a[j-1]=t;
     flag=1;
    }
   }
  }
  for(i=0;i<n;i++)
  {
   printf("%d",a[i]);
   if(i<n-1)
    printf(" ");
   else
    printf("\n");
  }
 }
 return 0;
}

  

希尔排序是一种按照增量排序的方法。

其中增量值是小于n的正整数。

  shell排序的基本思想[1]是:

    先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为dl的倍数的记录放在同一个组中。先在各组内进行直接插人排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。

可以根据百度百科中提供的图来直观的看一下:

(1)初始增量为3,该数组分为三组分别进行排序。(初始增量值原则上可以任意设置(0<gap<n),没有限制)

(2)将增量改为2,该数组分为2组分别进行排序。

(3)将增量改为1,该数组整体进行排序。

下面是根据该数组实现的代码:

 

#include <iostream>

using namespace std;

int a[] = {70,30,40,10,80,20,90,100,75,60,45};

void shell_sort(int a[],int n);
int main()
{
	cout<<"Before Sort: ";
    for(int i=0; i<11; i++)
		 cout<<a[i]<<" ";
	  cout<<endl;
	  shell_sort(a, 11);
   cout<<"After Sort: ";
	  for(int i=0; i<11; i++)
		  cout<<a[i]<<" ";
	  cout<<endl;
	 system("pause");
}

void shell_sort(int a[], int n)
{
	int gap;
	for(gap = 3; gap >0; gap--)
	{
		for(int i=0; i<gap; i++)
		{
			for(int j = i+gap; j<n; j=j+gap)
			{
				if(a[j]<a[j-gap])
				{
					int temp = a[j];
					int k = j-gap;
					while(k>=0&&a[k]>temp)
					{
						a[k+gap] = a[k];
						k = k-gap;
					}
					a[k+gap] = temp;
				}
			}
		}
	}
}
 

  

选择排序:从所有序列中先找到最小的,然后放到第一个位置。之后再看剩余元素中最小的,放到第二个位置……以此类推

for(int i=0; i<v.size(); i++){
                int min = v[i]; 
                int temp;
                int index = i;
                for(int j=i+1;j<v.size();j++){
                    if(v[j] < min){ 
                        min = v[j]; 
                        index = j;
                    }       
                }       
        
                temp = v[i]; 
                v[i] = min;
                v[index]= temp;
        }

  堆排序:http://jingyan.baidu.com/article/5225f26b057d5de6fa0908f3.html(百度讲的很详细了)

  归并排序:

#include <iostream>
using namespace std;
typedef struct 
{
	int r[100+1];
	int length;
}SqList;
//二路归并
void Merge(SqList &L,int low,int m,int high)
{
	int i,j,k;
	i=low;//low为第一个有序区的第一个元素,i指向第一个元素
	j=m+1;//m为第一个有序区的最后一个元素,m+1为第二个有序区第一个元素,j指向第一个元素
	k=0;
	int *t=new int[high-low+1];//t数组用来存合并的序列
	if(!t)
	{
		cout<<"ERROR!";
		return;
	}
	while(i<=m&&j<=high)//顺序选取两个有序区的较小元素,存储到t数组中
		if(L.r[i]<=L.r[j])
			t[k++]=L.r[i++];
		else
			t[k++]=L.r[j++];
	while(i<=m)//若比较完之后,第一个有序区仍有剩余,则直接复制到t数组中
		t[k++]=L.r[i++];
	while(j<=high)//同上
		t[k++]=L.r[j++];
	for(i=low,k=0;i<=high;i++,k++)
		L.r[i]=t[k];
	delete []t;
}
//分治
void MergeSort(SqList &L,int low,int high)
{
	int mid;
	if(low<high)
	{
		mid=(low+high)/2;
		MergeSort(L,low,mid);
		MergeSort(L,mid+1,high);
		Merge(L,low,mid,high);
	}
}
void MSort(SqList &L)
{
	MergeSort(L,1,L.length);
}
void main()
{
	//freopen("in.txt","r",stdin);
	int i;
	SqList L;
	cin>>L.length;
	for(i=1;i<=L.length;i++)
		cin>>L.r[i];
	MSort(L);
	for(i=1;i<=L.length;i++)
		cout<<L.r[i]<<" ";
}

  

快速排序:

#include<stdio.h>

void sort(int *a, int left, int right)
{
    if(left >= right)/*如果左边索引大于或者等于右边的索引就代表已经整理完成一个组了*/
        return ;
    int i = left;
    int j = right;
    int key = a[left];

    while(i < j)                               /*控制在当组内寻找一遍*/
    {
        while(i < j && key <= a[j])
        /*而寻找结束的条件就是,1,找到一个小于或者大于key的数(大于或小于取决于你想升
        序还是降序)2,没有符合条件1的,并且i与j的大小没有反转*/
            j--;/*向前寻找*/

        /*找到一个这样的数后就把它赋给前面的被拿走的i的值(如果第一次循环且key是
        a[left],那么就是给key)*/

        while(i < j && key >= a[i])
        /*这是i在当组内向前寻找,同上,不过注意与key的大小关系停止循环和上面相反,
        因为排序思想是把数往两边扔,所以左右两边的数大小与key的关系相反*/
            i++;

        int temp;
        temp=a[left];
        a[left]=a[i];
        a[i]=temp;
    }

    a[i] = key;/*当在当组内找完一遍以后就把中间数key回归*/

    sort(a, left, i - 1);/*最后用同样的方式对分出来的左边的小组进行同上的做法*/
    sort(a, i + 1, right);/*用同样的方式对分出来的右边的小组进行同上的做法*/
                       /*当然最后可能会出现很多分左右,直到每一组的i = j 为止*/
}
int main()
{
    int n,a[100],i;
    while(scanf("%d",&n)!=EOF)
    {
        for(i=0;i<n;i++)
            scanf("%d",&a[i]);
        sort(a,0,n-1);
        for(i=0;i<n;i++)
        printf("%d",a[i]);
    }
    return 0;
}

  也可以qsort直接解决了