【数据结构之内部排序】插入排序、快速排序、选择排序、归并排序

排序方法分为两大类:一类是内部排序,指的是待排序记录存放在计算机随机存储器中进行的排序过程;另一类是外部排序,指的是待排序记录的数量很大,以致内容一次不能容纳全部记录,在排序中尚需对外存进行访问的排序过程。

内部排序按照排序过程所需的工作量来区别的话,可分为三类:(1)简单的排序方法,其时间复杂度为O(n^2); (2)先进的排序方法,其时间复杂度为O(nlogn); (3)基数排序,其时间复杂度为O(d*n);

这里主要就三类每一类介绍一两个经典的算法,其中均采用顺序存储结构进行操作。

1.简单排序

 

(1)直接插入排序(Straight Insertion Sort)

直接插入排序的基本操作是从一个有序的表中插入一个新元素,从而得到一个新的有序表。

算法如下:

 

 1 void insertsort(int *array, int num)
 2 {
 3     int temp, i, j;
 4     for (i = 1; i < num; i++)
 5     {
 6         if (array[i] < array[i - 1])        //当插入元素比前面元素小
 7         {
 8             temp = array[i];
 9             for (j = i - 1; j >= 0 && array[j] > temp; j--)       //将前i个比插入元素小的元素全部往后挪一位,然后插入待插入元素
10                     array[j + 1] = array[j];
11             
12             array[j + 1] = temp;
13         }
14     }    
15 }

 

 

(2)折半插入排序(Binary Insertion Sort)

直接插入排序适合于当待排序记录的数量n很小时使用,但是当n很大时,则不宜使用直接插入,此时可以使用折半插入排序。

 

算法思想:

  将一个元素插入有序表中,找到表头元素和表尾元素,设m为中间元素,比较中间元素m与待插入元素,若m大,则在m到表尾元素中寻找插入位置,否则在表头到m中寻找插入位置,重复该过程,知道最终行成新有序表。

 

算法如下:

 1 void BinaryInsertSort(int *a, int n)   
 2 {
 3     int i, k, low, high, temp, m ;
 4     for(i = 1; i < n; i++)
 5     {
 6         low = 0;
 7         high = i - 1;
 8 
 9         while(low <= high) 
10         {
11             m = (low + high) / 2;
12             if(a[m] > a[i]) high = m - 1;
13             else low = m + 1;
14         }
15          int temp = a[i];
16          for(k = i; k > m; k--)
17               a[k] = a[k-1];
18          a[high + 1] = temp;
19     }
20 }            

 

 

总结:折半插入排序所需附加存储空间和直接插入排序相同,从时间上比较,折半插入仅仅减少关键字间的比较次数,而记录的次数不表,因此,两种算法的时间复杂度都是O(n^2)。

 

 

2.快速排序

 

(1)冒泡排序(Bubble Sort)

冒泡排序过程很简单,从第一个元素开始,每次和后面的元素逐个比较,如果比后面元素大则交换位置,每趟冒泡把一个最大的元素放到最后,最后形成新的有序表。

算法如下:

 

 1 void BubbleSort(int *a,int n)
 2 {
 3     int i,j,temp;
 4     for (i=0;i<n;++i)
 5         for (j=0;j<n-i-1;++j)
 6         {
 7             if (a[j]>a[j+1])
 8             {
 9                 temp=a[j];
10                 a[j]=a[j+1];
11                 a[j+1]=temp;    
12             }    
13           }
14 }

 

 

(2)快速排序(Quick Sort)

快速排序是对冒泡排序的一种改进。

 

算法思路:通过一躺排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另外一部分的关键字要小,然后继续分别对这两部分重复该步骤,最后达到整个序列有序。

 

步骤:1.首先找到一个记录作为枢轴(pivot);

   2.以该枢轴将序列分割为两部分;

   3.以此类推;

 

关于找枢轴的方法,主要有一下三种:

1.固定位置:取序列的最后一个元素或第一个元素;

2.随机选取;

3.三数取中:使用左端、中间、右端三个中的中值作为枢轴。

 

这里举了选取最后一个元素作为枢轴的算法:

 1 int partion(int *a,int low,int high)
 2 {
 3     int x=a[high];
 4     int temp;
 5     int j,i=low-1;
 6     for (j=low;j<high;j++)
 7     {
 8         if (a[j]<x)              //如果元素比最后一个小,则交换
 9         {
10             i++;
11             temp=a[i];
12             a[i]=a[j];
13             a[j]=temp;
14         }
15     }    
16     temp=a[i+1];
17     a[i+1]=a[high];
18     a[high]=temp;
19 
20     return i+1;
21 }
22 
23 void Qsort(int *a,int low,int high)
24 {
25     if (low<high)
26     {
27         int pivotloc=partion(a,low,high);        //每次利用递归将每个小部分划分,最终达到有序
28         Qsort(a,low,pivotloc-1);
29         Qsort(a,pivotloc+1,high);
30     }
31 }

 

具体的分析如下:

e.g:给出5个数 2 7 8 1 5 6 4

第一趟划分后变成 2 1 4 7 5 6 8

然后利用递归,对4左边的序列2 1划分变成1 2,右边部分变成5 6 7 8,最后合并有序。

 

总结:冒泡排序的时间复杂度为O(n^2),而快速排序最佳时间复杂度为O(n*logn),最坏为O(n^2)。

 

3.选择排序

(1)简单选择排序(Seletion Sort)

算法思想:通过n-i次关键字间的比较,每一趟在n-i-1个记录中选取关键字最小的记录作为有序序列中的第i个记录,并和第i个记录交换值。


算法如下:

 

 1 void select_sort(int a[],int n)//n为数组a的元素个数
 2 {
 3     //进行N-1轮选择
 4     for(int i=0; i<n-1; i++)
 5     {
 6         int min_index = i; 
 7         //找出第i小的数所在的位置
 8         for(int j=i+1; j<n; j++)
 9         {
10             if(a[j] < a[min_index])
11             {
12                 min_index = j;
13             }
14         }
15         //将第i小的数,放在第i个位置;如果刚好,就不用交换
16         if( i != min_index)
17         {
18             int temp = a[i];
19             a[i] = a[min_index];
20             a[min_index] = temp;
21         }
22     }
23 }

 

 

 

4.归并排序

归并排序(Merging Sort)是将两个或两个以上的有序表组合成一个新的有序表。

算法思路:假设初始序列有n个记录,则可看成是n个有序的子序列,每个子序列的长度为1,然后两两合并,得到[n/2]个长度为2或1的有序子序列;再两两归并,如此重复最终活的一个长度为n的有序序列。

算法如下:

 

 1 void Merge(int arr[],int low,int mid,int high)
 2 {
 3     int i,k;
 4     int *temp=(int *)malloc((high-low-1)*sizeof(int));
 5     int left_low=low;
 6     int left_high=mid;
 7     int right_low=mid+1;
 8     int right_high=high;
 9 
10     for (k=0;left_low<left_high&&right_low<right_high;++k)
11     {
12         if (arr[left_low]<arr[right_low])        //从左右两个序列中挑选出最小的元素放在temp数组中
13               temp[k]=arr[left_low++];
14         else
15               temp[k]=arr[right_low++];
16      }
17 
18     //将挑选后剩余的元素放进temp数组
19     while (left_low<left_high)    
20         temp[k++]=arr[left_low++];
21     while (right_low<right_high)
22         temp[k++]=arr[right_low++];
23     
24     for (i=0;i<high-low-1;++i)          //将有序的temp数组复制到arr数组中
25         arr[low+i]=temp[i];
26     
27     free(temp);
28 }    
29 
30 void MergeSort(int arr[],int first,int last)
31 {
32     int m=0;
33     if (first<last)
34     {
35         m=(first+last)/2;
36         MergeSort(arr,first,m);              //将左序列继续划分为小部分
37         MergeSort(arr,m+1,last);             //将右序列继续划分为小部分
38         Merge(arr,first,m,last);
39     }
40 }    

 

 

 

 

总结:归并排序的时间复杂度为O(n*logn)。

 

 

 

各种内部排序算法的比较:

 

posted @ 2016-12-22 19:45  HugoNgai  阅读(3949)  评论(0编辑  收藏  举报