排序

引言

基本的算法问题是每个程序员需要掌握的基本功,无论是平时工作还是找工作都会遇到基本的算法问题。这个系列的文章主要是算法。其中大部分的内容是来自网络和自己总结,由于本人水平有限错误在所难免,如有问题请多多指教。

概述

算法是用来解决一类计算问题的,注意是一类问题,而不是一个特定的问题。排序与查找是基本的算法。

排序种类

每种算法都有它特定的使用场合,很难通用。
这里写图片描述

总结各种算法之前,现介绍下几个概念:

  1. 稳定度:稳定排序算法会依照相等的关键值维持纪录的相对次序。对于一个简单类型,数字值就是其全部意义,即使交换了也看不出什么不同。但是对于复杂的类型,交换的话可能就会使原本不应该交换的元素交换了。
  2. 空间复制度:复杂度是对一个算法在运行过程中临时占用存储空间大小的量度。
  3. 时间复杂度:是指执行算法所需要的计算工作量
  4. 算法的复杂度:当设计一个大型算法时,要综合考虑算法的各项性能,算法的使用频率,算法处理的数据量的大小,算法运行的机器系统环境等各方面因素,才能够设计出比较好的算法。算法的时间复杂度和空间复杂度合称为算法的复杂度。

排序的基本思想

(1)冒泡排序
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。

(2)选择排序
选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推。举个例子,序列5 8 5 2 9,第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。

(3)插入排序
插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。
这里写图片描述
(4)快速排序
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
这里写图片描述

(5)归并排序
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。

这里写图片描述
(6)基数排序
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用

假设有欲排数据序列如下所示:
73 22 93 43 55 14 28 65 39 81

第一次排序
这里写图片描述

第二次排序
这里写图片描述

(7)希尔排序(shell)
先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序。然后取第2个增量重复上述的分组和排序。直至所有的增量=1,排序完为止。
这里写图片描述
增量为5,2,1。

(8)堆排序
它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值。
用大根堆排序的基本思想

① 先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区
② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key
③由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。
……
直到无序区只有一个元素为止。

举例
初始化建堆
这里写图片描述

每次调整都是从父节点、左孩子节点、右孩子节点三者中选择最大者跟父节点进行交换
这里写图片描述

算法实现

冒泡排序

#include <stdio.h>
#include <stdlib.h>

int  bubbleSort(int a[], int n)
{
    int i,j;  
    for( i =0 ; i< n-1; ++i) 
    {  
        for( j = 0; j < n-i-1; ++j)
        {  
            if(a[j] > a[j+1])  
            {  
                int tmp = a[j];
                a[j] = a[j+1];
                a[j+1] = tmp;
            }  
        }  
    }  
}
int main()
{
    int i;
    int a[]={2,1,3,4,3};
    bubbleSort(a,5);
    for(i=0;i<5;i++)
    {
        printf("%d",a[i]);
    }   
    printf("\n");
}

选择排序

#include <stdio.h>
#include <stdlib.h>

int SelectMinKey(int a[], int n, int i)  
{  
    int j,k = i;  
    for(j=i+1 ;j< n; ++j) {  
        if(a[k] > a[j]) k = j;  
    }  
    return k;  
}  

void selectSort(int a[], int n){  
    int i, key, tmp;  
    for( i = 0; i< n; ++i) {

        //选择最小的元素       
        key = SelectMinKey(a, n, i);             
        if(key != i){  
            tmp = a[i]; 
            a[i] = a[key]; 
            a[key] = tmp; //最小元素与第i位置元素互换  
        }  
    }  
}

int main()
{   int i;
    int a[5] = {3,1,5,2,4};  
    selectSort(a, 5);  
    for(i=0;i<5;i++)
    {
        printf("%d",a[i]);
    }   
    printf("\n");
}

插入排序

#include <stdio.h>
#include <stdlib.h>

void InsertSort(int a[], int n)  
{  
    int i,j,x;
    for(i= 1; i<n; i++)
    { 

        // 若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入  
        if(a[i] < a[i-1])
        {
            j= i-1;   
            x = a[i];        //复制为哨兵,即存储待排序元素  
            a[i] = a[i-1];           //向后移一个元素  
            //查找在有序表的插入位置  
            while(x < a[j])
            {  
                a[j+1] = a[j];  
                j--;         //元素后移  
            }  
            a[j+1] = x;      //插入到正确位置  
        }  
    }  

}  

int main()
{  
    int i;
    int a[5] = {2,1,5,4,3};  
    InsertSort(a,5);
    for(i=0;i<5;i++)
    {
        printf("%d",a[i]);  
    }
    printf("\n");  
}
#include <stdlib.h>
#include <stdio.h>
void swap(int *a, int *b)  
{  
    int tmp = *a;  
    *a = *b;  
    *b = tmp;  
}  

int partition(int a[], int low, int high)  
{   
   //基准元素
   int privotKey = a[low];                              

   //从表的两端交替地向中间扫描
   while(low < high)
   {                                  

        //从high 所指位置向前搜索,至多到low+1 位置。将比基准元素小的交换到低端  
        while(low < high  && a[high] >= privotKey) --high;  
        swap(&a[low], &a[high]);  
        while(low < high  && a[low] <= privotKey ) ++low;  
        swap(&a[low], &a[high]);  
    }  
    return low;  
}  


void quickSort(int a[], int low, int high)
{  
    if(low < high)
    {  
        int privotLoc = partition(a,  low,  high);  
        //将表一分为二  
        quickSort(a,  low,  privotLoc -1);          
        //递归对低子表递归排序  
        quickSort(a,   privotLoc + 1, high);
        //递归对高子表递归排序  
    }  
}  

int main()
{   
    int i;
    int a[5] = {3,1,5,4,2};  
    quickSort(a,0,5);  

    for(i=0;i<5;i++)
    {
        printf("%d",a[i]);
    }  
    printf("\n");
}

算法的选择

(1)若n较小(如n≤50),可采用直接插入或直接选择排序。
 
(2)若文件初始状态基本有序,则选用直接插入、冒泡或随机的快速排序。

(3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。

说明:
快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短 。  
堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值