排序

参考:常用排序及过程可视化 图解快速排序 图解堆排序 八大排序算法

注意:比较排序数学意义上的最优时间复杂度为nlgn

stl提供的排序算法有sort,可以自定义比较函数,假设有一个student类,要根据学号成绩排序,student和int不同,没有一定需要重载<操作符的必要,即使重载,也应该是比较学号。解决办法是定义一个全局的比较函数,传递给sort(s.begin(),s.end(), foo);这儿不能传递仿函数less<student>,因为less也是对student对象作<操作(如果student重载的<是按学号比较的)。仿函数是类内重载了()操作符,传递给sort的是个临时对象,sort(s.begin(),s.end(), less<int>())。仿函数比全局比较函数好的地方在于没有全局的变量,仿函数本身是类,可以保存对象状态。 STL仿函数

部分排序partial_sort(v.begin(),v.begin()+3,v.end(),greater<int>()) 可以对0-3部分进行降序排列,得到最大的前3个数。

nth_element(vect.begin(), vect.begin()+3, vect.end(),less<student>()); 得到倒数第4名成绩的学生。

STl中的排序算法

用仿函数或者自定义全局比较函数以及重载<操作的方式都有缺点,如程序显得杂乱,sort的第三个参数是谓词,lambda表达式也是谓词,sort(a,a+n,[](int x,int y){return x<y})。

冒泡排序:在一趟比较交换过程中没有发生交换,则序列已经有序。可以从前往后比较交换,或者从后向前compare and swap;O(n^2)的时间复杂度已经很高,线性时间复杂度O(n)<O(nlogn)<O(n^2),最小的是常数时间——不随n的规模扩大而变化;如果交换条件改为A[j] > =A[j +1] ,会变化为unstable sort.

时间复杂度分析:1)最坏情况,待排序列逆序,内层循环每次都发生交换,交换次数为n-i(外层循环发生n-1次),总次数为等差数列的和:(n-1+1)*(n-1)/2=n(n-1)/2,时间复杂度为O(n^2) ;2)最好情况,序列有序,内层循环执行n-1次比较后(不执行交换)就结束了,时间复杂度为O(n);所以平均时间复杂度为O(n^2)。

输入数组为:int a[] = { 2, 6, 95, 21, 35, 12, 11, 3, 78, 21, 14, 0 };

 1 void bubllesort(int arr[],int n) //从头向后比较交换位置,降序排列
 2 {
 3     for (int i = 0; i < n; i++)
 4     {
 5         bool change = false;
 6         for (int j = 0; j < n - i-1; j++)
 7         {
 8             if (arr[j] < arr[j + 1]) {
 9                 std::swap(arr[j], arr[j + 1]); change = true;
10             }
11         }
12         if (change == false) break;//如果一趟没有发生交换,则已经全部有序,需要中断循环
13     }
14 }
15 void bublesort2(int A[], int n)//从后向前冒泡
16 {
17     for (int i = 0; i < n-1; i++) //控制比较趟数
18     {
19         bool flag = false;
20         for (int j = n - 1; j > i; j--) //每一趟的比较
21         {
22             if (A[j] > A[j - 1]){
23                 std::swap(A[j], A[j - 1]); flag = true;
24             }  // 如果条件改成A[i] >= A[i + 1],则变为不稳定的排序算法
25         }
26         if (flag == false) return;
27     }
28 }
29 
30 BubbleSort
BubbleSort

 

 选择排序:从头开始,每次选择最小的元素,交换最小的和第一个的位置;选择排序是不稳定的排序算法,不稳定发生在最小元素与A[i]交换的时刻。比如序列:{ 5, 8, 5, 2, 9 },一次选择的最小元素是2,然后把2和第一个5进行交换,从而改变了两个元素5的相对次序;

 1 void selectionsort(int A[], int n)
 2 {
 3     for (int i = 0; i < n-1; i++)
 4     {
 5         int min = A[i];
 6         int index = i; //最小元素的索引
 7         for (int j = i+1; j < n; j++)//从A[i]下一个元素开始比较
 8         {
 9             if (A[j] < min) { min = A[j]; index = j; }
10             
11 
12         }
13         A[index] = A[i];
14         A[i] = min;
15     }
16 }
SelectionSort

 插入排序:一趟排序不能保证元素到达了最终位置,冒泡、选择一趟就是最终位置。

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到该位置后
  6. 重复步骤2~5。
 1 void insertsort2(int A[], int n)
 2 {
 3     for (int i = 1; i < n; i++)
 4     {
 5         int temp = A[i];
 6 
 7         int j = i - 1;
 8         for (; j >= 0; j--)
 9         {
10             if (A[j] > temp) A[j + 1] = A[j]; //每次后移一个位置 //写了一个错误语句A[j]>A[[i]
11             /*else break;*/
12             else {
13                 A[j + 1] = temp;  break;
14             }
15         }
16         /*A[j + 1] = temp;*/
17         if (j == -1) A[0] = temp;
18         for (int i = 0; i < n; i++){
19             cout << A[i] << " ";
20         }
21         cout << endl;
22         
23         
24     }
25 }
从后朝前寻找插入位置
 1 void insertsort(int A[], int n) //升序,在有序的序列总寻找插入位置
 2 {
 3     for (int i = 1; i < n; i++)//序列A[0]~A[i-1]已经有序
 4     {
 5         int temp = A[i];
 6         for (int j = 0; j < i; j++) //j 范围[0,i-1]
 7         {
 8             if (A[j] <= A[i]) continue;
 9             else //从此时的j到i要开始后移 
10             {
11                 for (int k = i; k > j; k--)  //从前向后寻找插入位置需要写三层循环
12                 {
13                     A[k] = A[k - 1];
14                 }
15                 A[j] = temp;
16                 break;
17 
18             }
19 
20         }
21     }
22 
23 }
从前向后寻找插入位置需要三层循坏

 

 1 void insertsort3(int A[], int n)
 2 {
 3     for (int i = 1; i < n; i++)
 4     {
 5         int temp = A[i];
 6 
 7         int j = i - 1;
 8         while (j >= 0 && A[j]>temp)//此处必须写temp,A[i]会更新
 9         {
10             A[j + 1] = A[j];
11             j--;
12         }
13         /*循环结束时:一、j=-1,即插入位置在0;二、j=i-1;三、插入位置在j+1*/
14         A[j + 1] = temp;
15 
16     }
17 }

 快速排序:1、选一个基准元素(一般选择最左边的元素),将序列划分了左右两部分,左边小于基准,右边大于基准

      2、对左右子序列重复1的过程,直至序列子序列有序(这对拿来做编程结束点没有帮助)

分析为什么最终会全部有序:序列划分为左右子序列,这两个区间相互是有序的,继续划分区间,所有的区间都有序,直至区间内部也有序,序列整体就有序了。

代码的整体应该是: 此题的难点在于步骤1,如何交换形成相对基准的左右序列;执行语句写在前面,递归调用语句写在后面,先完成一趟划分,在递归进入左右序列进行划分

  1. 完成一次关于某个基准的左右划分(简单想法是基准两边两个指针,扫描到右边小于基准的,左边大于基准的,就交换元素,但这样行不通,如右边有小的,左边没有大的,这时无法交换)
  2.  quickSort(a, 0,i); quickSort(a,i+1,right) ,此时就是递归深入去划分子区间
 1 void quickSort(int a[], int left, int right)
 2 {
 3     if (left < right)
 4     {
 5         int i = left;
 6         int j = right;
 7         int temp = a[i];//每次选最左边元素作为基准
 8         while (i != j)
 9         {
10             
11             //因为在ij移动过程中,有可能出现不满足i!=j的条件
12             while (i < j&&a[j] > temp){ //如果等于的情况怎么办
13                 j--;
14             }
15             //可能出现i=j的终止情况 ,用if判断保证循环结束时a[j]<或者=temp
16             if (i < j) {
17                 a[i] = a[j]; i++; //i右移一位,原来的a[i]被temp记录了,不会丢失
18             }
19             while (i < j && a[i] < temp) //换个扫描方向
20             {
21                 i++;
22             }
23             if (i < j)
24             {
25                 a[j] = a[i];
26                 j--;
27             }
28         }
29         //完成了一趟循环,此时i=j的位置为基准的最终位置
30         a[i] = temp;
31         cout << "" << i <<"个基准"<< a[i] << " ";
32         quickSort(a, 0, i - 1);
33         quickSort(a, i + 1, right);
34     }
35 }

 归并排序 :自顶向下的方法,把数组划分了左右数组,一直递归到单个元素序列有序,递归到上层后再合并左右数组。有个关键问题是在对两个有序数组归并的函数中(这两个数组其实是连续的),申请大小为两数组之和的临时数组,存排序结果——不申请额外数组,在原有数组上就变成插入排序了。递归划分的结果是二叉树,深度为lgn,这样每层都会使用n个空间,总共使用nlgn空间,那是否空间复杂度变成了O(nlgn)?这一点暂时不能确定,如果提供一个大小为n的数组,每次都读写该数组,辅助空间就是n。

自底向上的方法是用循环写

参考 http://www.acmerblog.com/merge-sort-5372.html  http://blog.csdn.net/yuzhihui_no1/article/details/44223225 (我的代码和这个一样) http://blog.csdn.net/u013074465/article/details/42043967 http://www.cnblogs.com/chengxiao/p/6194356.html

 1 #include <algorithm>
 2 #include <functional>
 3 #include <array>
 4 #include <iostream>
 5 int temp[100]; //全局的辅助空间
 6 void merge(int a[], int l, int mid, int r){
 7     //a[l] -a[mid] a[mid+1]-a[r]
 8     int i = l; 
 9     int j = mid + 1;
10     int k = 0;
11     while ((i <= mid)&&(j <= r)){
12         if (a[i] <=a[j]) {
13             temp[k++] = a[i++];
14         }
15         else {
16             temp[k++] = a[j++];
17         }
18     
19     }
20     if (j<=r&&i==mid+1) //这个if语句是可以简化掉的,只用写while循环
21         while (j <= r) temp[k++] = a[j++]; 
22     if (i<=mid&& j==r+1)
23         while (i <= mid) temp[k++] = a[i++]; 
24     
25     for (i = l, k = 0; i <= r; i++,k++){
26         a[i] = temp[k];
27 
28     }
29     cout << endl;
30     for (int k = l; k < r; k++){
31         cout << a[k] << " ";
32     }
33     
34     cout << endl;
35 }
36 void mergeSort(int a[], int l, int r){
37     if (l < r){
38         int mid = (l + r) / 2;
39         mergeSort(a, l, mid);
40         mergeSort(a, mid + 1, r);
41         merge(a, l, mid, r);
42     }
43 }
44 int main(){
45     int a[] = { 49, 38, 65, 97, 76, 13, 27, 49 };
46     int c = -1;
47     
48     
49     mergeSort(a, 0, 7);
50     for (auto x : a){
51         cout << x << " ";
52     }
53 
54 }

 对比归并与快排的两种思路,都是递归排序,分解问题的过程是不同的,归并一直分解到元素个数为1,自然有序,然后从底向上合并返回;快排每一次都把序列分为相对有序的两部分,然后继续分解,最终所有子序列有序时整体也就有序了。归并是稳定排序,快排不是,且归并需要消耗额外的空间。

posted @ 2017-08-14 20:40  hchacha  阅读(207)  评论(0)    收藏  举报