基础数据结构和算法8:高级排序算法

1. 归并排序

1.1 步骤

  1. 实现两个有序数组的合并
    void merge(int arr[],int n,int mid);
    
  2. 拆分并合并数组
    void merge_sort(int arr[],int n);
    

1.2 参考代码

void merge(int arr[],int n,int mid){
    int temp[n];
    memcpy(temp,arr,n*sizeof(int));
    int p = 0,q = mid,k = 0;
    while(p<mid && q<n) arr[k++] = temp[p]<=temp[q]?temp[p++]:temp[q++];
    if(p<mid) memcpy(arr+k,temp+p,(mid-p)*sizeof(int));
    if(q<n) memcpy(arr+k,temp+q,(n-q)*sizeof(int));
}
void merge_sort(int arr[],int n){
    if(n <= 1) return;
    int mid = n/2;
    merge_sort(arr,mid);
    merge_sort(arr+mid,n-mid);
    merge(arr,n,mid);
}

1.3 时间复杂度

一共拆分log_2n次,每次比较n个元素,一共比较nlog_2n次。

1.4 空间复杂度

随着n的增长,排序需要增加额外空间n+log_2n(临时数组n和递归调用函数栈log_2n),空间复杂度为O(n)

1.4 迭代归并排序

void Merge(int* arr,int n,int mid){
     int res[n];
     int i=0,j=mid,k=0;
     while(i<mid && j<n){
         res[k++] = (arr[i]<arr[j])?arr[i++]:arr[j++];
     }
     if(i<mid) memcpy(res+k,arr+i,(mid-i)*sizeof(int));
     if(j<n) memcpy(res+k,arr+j,(n-j)*sizeof(int));
     memcpy(arr,res,n*sizeof(int));
}
void SubMerge(int* arr,int n,int step){
    int len = n;
    for(int i=0;i+step<n;i+=step*2){ // merge index
    Merge(arr+i,min(2*step,len),step);
    len -= 2*step;
    }
}
void MergeSort(int* arr,int n){
     for(int i=1;i<n;i*=2){
         SubMerge(arr,n,i);
     }
}

练习

剑指 Offer 51. 数组中的逆序对


2. 快速排序

  • 左游标是i哨兵,右游标是j哨兵。
  • 第一次交换


  • 第二次交换


  • 基准交换


2.1 步骤

  1. 根据基准元素重排数组
    int partition(int arr[],int n);
    
  2. 依次排列两个部分
    void quick_sort(int arr[],int n);
    

2.2 参考代码

int partition(int arr[],int n){
    int key = arr[0];
    int p = 0,q = n-1;
    while(p<q){
        while(p<q && arr[q]>=key) q--;
        arr[p] = arr[q];
        while(p<q && arr[p]<=key) p++;
        arr[q] = arr[p];
    }
    arr[p] = key;
    return p;
}
void quick_sort(int arr[],int n){
    if(n<=1) return;
    int pivot = partition(arr,n);
    quick_sort(arr,pivot);
    quick_sort(arr+pivot+1,n-pivot-1);
}

2.3 时间复杂度

一共拆分log_2n次,每次比较n个元素,一共比较nlog_2n次。

2.4 空间复杂度

随着n的增长,排序需要增加额外空间log_2n(递归函数栈空间),空间复杂度为O(log_2n)

2.5 优化

交换指针法

int partition(int arr[],int n){
    int key = arr[0];
    int p = 0,q = n-1;
    while(p<q){
        while(p<q && arr[q]>=key) q--;
        while(p<q && arr[p]<=key) p++;
        if(p>=q) break;
        swap(arr+q,arr+p);
    }
    swap(arr,arr+q);
    return q;
}

3. 希尔排序

3.1 原理

  1. 给定一个长度n的列表,选择一定的步长gap,将列表分成若干个子列表sublist
    例如:长度n=9步长gap=3分成3个子列表sublist
  2. 对每一个子列表sublist进行插入排序。
  3. 依次减小步长gap,重复上述操作。直到gap1

希尔排序比插入排序的优势:
通过分组排序使元素逐渐靠近最终位置,从而减少了插入排序 时的移动次数。(先粗调再微调

3.2 步骤

  1. 划分间距gap并执行排序
    void shell_sort(int arr[],int n);
    
  2. 根据间距gap执行插入排序
    void insertion_sort(int arr[],int n,int gap);
    
  3. 根据间距gap插入
    void insert(int arr[],int n,int gap);
    

3.3 参考代码

void insert(int arr[],int n,int gap){
    for(int i=n-1-gap;i>=0;i-=gap){
        if(arr[i+gap]<arr[i]){
            swap(arr+i+gap,arr+i);
        }else{
            break;
        }
    }
}
void insertion_sort(int arr[],int n,int gap){
    for(int i=gap;i<=n;++i){
        insert(arr,i,gap);
    }
}
void shell_sort(int arr[],int n){
    int gap = n;
    do{
        gap = gap/2;
        insertion_sort(arr,n,gap);
    }while(gap>1);
}

比较:插入排序与希尔排序


3.5 优化

移动代替交换

void insert(int arr[],int n,int gap){   
    int key=arr[n-1];
    int i=0;
    for(i=n-1;i>=gap&&arr[i-gap]>key;i-=gap){
        arr[i] = arr[i-gap];
    }
    arr[i] = key;
}

void insertion_sort(int arr[],int n,int gap){
    for(int i=gap;i<n;++i){// 插入元素i操作
        insert(arr,i+1,gap);
    }
}

void shell_sort(int arr[],int n){
    for(int i=n/2;i>0;i/=2){// gap序列
        insertion_sort(arr,n,i);
    }
}

4. 小结

No. 算法 Algorithm Time Complexity Space Complexity Stable
1 快速排序 Quicksort O(nlog_2n) O(nlog_2n) No
2 归并排序 Mergesort O(nlog_2n)) O(n) Yes
3 希尔排序 Shell Sort O(n^{1.3}) O(1) No

5. 算法选择标准

如何选择排序算法?(定性)

No. 准则 排序算法
1 很少的元素 插入排序
2 几乎有序的元素 插入排序
3 关注最坏的情况 堆排序
4 希望能够得到一个好的平均情况下性能 快速排序
5 元素是从一个密集集合中抽取出 桶排序
6 希望尽可能少的写代码 插入排序

6. 练习

可以保持在原有时间复杂度前提下对双向链表进行排序的算法有:(不定项)

  1. 堆排序
  2. 并归排序
  3. 快速排序
  4. 希尔排序

可以保持在原有时间复杂度前提下对单向链表进行排序的算法有:(不定项)

  1. 选择排序
  2. 插入排序
  3. 快速排序
  4. 冒泡排序
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,511评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,495评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,595评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,558评论 0 190
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,715评论 3 270
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,672评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,112评论 2 291
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,837评论 0 181
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,417评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,928评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,316评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,773评论 2 234
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,253评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,827评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,440评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,523评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,583评论 2 249

推荐阅读更多精彩内容