数据结构与算法之七大排序总结

来源:互联网 发布:虾米音乐如何解绑淘宝 编辑:程序博客网 时间:2024/06/05 09:35

排序基本概念:
稳定性:假设Ki = Kj(1<=i<=n,1<=j<=n ,i != j),且在排序前的序列中 ri 领先于 rj(即 i < j)。如果排序后 ri 仍领先于 rj,则称所用的排序方法是稳定的;反之,若可能使得排序后的序列中 rj 领先于 ri,则称所用的排序方法是不稳定的。
排序分类:
1)内排序:在排序整个过程中,待排序的所有记录全部被放置在内存中。
2)外排序:是由于排序的记录个数太多,不能同时放置在内存中,整个排序过程需要在内外存之间多次交换数据才能进行。
对内排序来说,排序算法的性能主要受3个方面影响:
1)时间性能:
排序算法的时间开销是衡量其好坏的最重要标志。
在内排序中,主要进行两种操作:比较移动
高效率的内排序算法应该是具有尽可能少的关键字比较次数和尽可能少的记录移动次数。
2)辅助空间
评价排序算法的另一个主要标准是:执行算法所需要的辅助存储空间。辅助存储空间是除了存放待排序所占用的存储空间之外,执行算法所需要的其他存储空间。
3)算法的复杂性,即算法本身的复杂度

内排序分为:插入排序、交换排序、选择排序和归并排序。
冒泡排序、简单选择排序 和 直接插入排序 属于 简单算法;
希尔排序、堆排序、归并排序、快速排序 属于 改进算法。

学习排序算法中的几个体会:
1、学习排序,关键在于对算法的理解,对算法所执行的排序过程的理解,死记算法代码是不行的,我在学习的过程中深有体会。
2、一定要先把简单算法学会,即深刻理解冒泡、简单选择和直接插入这三种排序算法。后面的希尔排序、堆排序、快排等都是对前面算法的改进,进一步提高了其算法效率。
3、以下为我每次面对后四个改进算法的第一反应:
(1)希尔排序:分组插入,设定一个增量,改进直接插入算法。
(2)堆排序:构造大顶堆或小顶堆(大顶堆根节点数据为最大值,小顶堆为最小值),每次选择第一个数据,放到最后,剩余的数据重新构造顶堆,重复前面的操作。(即将简单选择算法中的min或max,通过构造顶堆来得出)
(3)归并排序:数列的递归分解(分解成有序数列)、合并有序数列。(关键要懂得怎么合并两个有序数列)
(4)快速排序:选定基准,挖坑填数。属于对冒泡排序的改进。对于这个算法的理解,建议看这一篇blog:http://blog.csdn.net/morewindows/article/details/6684558

七大排序算法时间与空间复杂度总结:
这里写图片描述

冒泡排序代码:
分三种情况:
第一种,不算正宗,如果是从小到大:i 每一次都是取最小的哪一个。
第二种,正宗冒泡排序,从后向前,比大小,再两两交换。
第三种,改进的冒泡排序,增加了一个flag,减少了不必要的交换。

/**简单冒泡排序0:从小到大*/void Bubble_sort0(int a[],int n){    int i,j;    for(i = 0; i < n - 1; i++)    {        for(j = i + 1; j < n; j++)        {            if(a[i] > a[j])                swap(a,i,j);        }    }}/**传统冒泡排序1:从大到小*/void Bubble_sort1(int a[],int n){    int i,j;    for(i = 0; i < n; i++)    {        for(j = n - 1; j > i; j--)        {            if(a[j - 1] < a[j])                swap(a,j-1,j);        }    }}/**改进冒泡排序2:从小到大*/void Bubble_sort2(int a[],int n){    int i,j;    bool flag;    flag = true;    for(i = 0; i < n && flag; i++)    {        flag = false;        for(j = n - 1; j > i; j--)        {            if(a[j - 1] > a[j])            {                swap(a,j-1,j);                flag = true;            }        }    }}

简单选择排序代码:
每一轮取最大或最小的那一个

/**简单选择排序:从小到大*/void Select_sort(int a[],int n){    int i,j,min;    for(i = 0; i < n - 1; i++)    {        min = i;        for(j = i + 1; j < n; j++)        {            if(a[min] > a[j])                min = j;   /**找到最小的标记就好*/        }        if(i != min)            swap(a,min,i); /**一轮下来再做交换*/    }}

直接插入排序代码:
类似于玩扑克时,边摸牌,边理牌

/**直接插入排序:从小到大*/void Insert_sort(int a[],int n){    int i,j,temp;    for(i = 1; i < n; i++)    {        temp = a[i];        for(j = i - 1; j >= 0 && a[j] > temp; j--)        {                a[j+1] = a[j];        }        /** 注释部分,这种写法是错误的,必须把判断条件写上面,因为要移动数据(思考一下就明白了)        for(j = i - 1; j >= 0; j--)        {            if(a[j] > temp)  // 写下面,会产生前面的移动,后面的不动                a[j+1] = a[j];        }        */        a[j+1] = temp;  /**注意这里是j+1*/    }}

希尔排序代码:
分组插入,可对比直接插入排序代码

/**希尔排序:从小到大,实为分组插入排序,增量递减*//**对分割的每一组进行插入排序*/void Shell_sort(int a[],int n){    int i,j,inc,temp; /**inc:增量*/    inc = n / 2;    while (inc >= 1)    {        for(i = inc; i < n; i++)        {            temp = a[i];            for(j = i - inc; j >= 0 && a[j] > temp; j = j - inc)  /**注意这里不是j--,而是j - inc*/            {                a[j + inc] = a[j];            }            a[j + inc] = temp;        }        inc = inc / 2;    }}

堆排序代码:
需了解二叉树性质5
将原无序构建成堆,排序过程中构建堆,堆排序
构造堆:数据的下沉与上浮

/**堆排序:构建堆,进行选择排序*//**调整大顶堆:以第i个结点作为根结点,调整二叉树为大顶堆*//**构造大顶堆是为了使排序结果从小到大,它与构造小顶堆不同,左右孩子取最大的那个*/void Heap_adjust(int a[],int i,int n){    int j,temp;    temp = a[i];      /**保存根结点数值*/    j = 2*i + 1;      /**j:左孩子节点下标,j+1:右孩子结点下标*/    while(j < n)      /**这里一定是 j  < n,不能够是 j+1,因为,没有右孩子,不代表没有左孩子,有左孩子的情况下还是要进入*/    {        if(a[j] < a[j+1] && j + 1 < n)            j = j + 1;                 /**j:取左右孩子结点中最大的那个结点的下标*/        if(a[j] <= temp)              /**这里蛮关键的,为跳出循环设置了一个条件,同时引起下文*/            break;        a[i] = a[j];                 /**这里应用了上面选择语句的跳出条件*/        i = j;        j = 2*i + 1;    }    a[i] = temp;                    /**注意这里不是j,因为在上文已有 i = j*/}/**将无序数列构造成大顶堆*/void Heap_build(int a[],int n){    int i;    for(i = (n - 1) / 2; i >= 0; i--) /**从最后一个有孩子的节点向上走*/        Heap_adjust(a,i,n);}/**堆排序:从小到大*/void Heap_sort(int a[],int n){    int i;    for(i = (n - 1) / 2; i >= 0; i--)        Heap_adjust(a,i,n);    for(i = n - 1; i >= 1; i--)    /**这里i >= 1,就好,不用是0,最后一个不用再调*/    {        swap(a,0,i);               /**第一个数和最后一个数交换*/        Heap_adjust(a,0,i);        /**每次从根结点开始调整,总结点数减1*/    }}

归并排序代码:
知道如何:将两个有序数列合并成一个有序数列
递归分解与合并:

/**下面这个函数,仅为让你了解如何合并数组,将a[]和b[]合并成c[]*/void Merge_array_test(int a[],int n,int b[],int m,int c[]){    int i,j,k;    i = j = k = 0;    /**这一步不要忘了*/    while (i < m && j < n)    {        if(a[i] > b[j])        {            c[k] = b[j];            k++;            j++;            // c[k++] = b[j++];        }        else{            c[k] = a[i];            k++;            i++;            // c[k++] = a[i++];        }        while (i < m)            c[k++] = a[i++];        while (j < n)            c[k++] = b[j++];    }}/**归并排序:递归分解 和 合并有序数列*//**归并排序中,分解的数组合并*/void Merge_array(int a[],int begin,int mid,int end,int temp[]){    int i,j,k;    int m,n;    m = mid;    n = end;    i = begin;    j = mid + 1;    k = 0;     /**这一步不要忘了*/    while (i <= m && j <= n)    {        if(a[i] < a[j])            temp[k++] = a[i++];        else            temp[k++] = a[j++];    }    while(i <= m)            temp[k++] = a[i++];    while(j <= n)            temp[k++] = a[j++];    for(i = 0; i < k; i++)         /**注意这里是i < k;而不是i < end;,i从begin开始*/        a[i + begin] = temp[i];    /**temp是从 0 到 k,a是从begin到end没错,但要兼顾两者,因此是这样写*/    /**两种错误写法    for(i = 0; i <= k; i++)   这里不能是 i <= k,因为前面是k++,k在结束后,还自增了一        a[i] = temp[i];    for(i = begin; i < k; i++)        a[i] = temp[i];    */    /**    for(i = begin;i <= end;i++)        a[i] = temp[i];     没有兼顾两者    */}/**递归,合并,排序*/void merge_sort(int a[],int begin,int end,int temp[]){    /**    int i,j,mid;    int *p;  这里不要加*p,最好新建一个函数,将创建临时数组放外面,不然在递归中,会一直创建temp[]    p = new int [end + 1];    mid = end / 2;    while ()   已经是递归调用了,怎么还用循环    {        Merge_sort(a,begin,mid);        Merge_sort(a,mid + 1,end);        Merge_array(a,begin,mid,end,p)    }    */    int mid;    if(begin < end)    {        mid = (begin + end) / 2;         /*这里为什么是begin+end的原因:比如一个数列 0 3 6,怎么取3-6之间的数呢?不是(6-3)/2,而应该是(6+3)/2 */        merge_sort(a,begin,mid,temp);    /**将一个数组变成两个,再通过递归,将其中一个再变成两个更小的,依次类推*/        merge_sort(a,mid+1,end,temp);        Merge_array(a,begin,mid,end,temp);    }}/**因为要加临时数组*/bool Merge_sort(int a[],int n){    int *p;    p = new int[n];    if(p == NULL)           /**或者写 if(!p),不要写成 if(p)*/        return false;    merge_sort(a,0,n-1,p);  /**创建一个临时数组,供所有分割的数组使用,由于有下标,它们都是使用下标对应的那部分空间*/    show(a,n);              /**这样就不用每合并两个数组,就创建一个临时数组*/    delete[] p;             /**注意不要少加了[]*/    return true;}

快速排序代码:
选好基准,挖坑填数

/*一种错误的写法:需要铭记:void Quick_sort_test(int a[],int l,int r){    int i,j,x;    x = a[l];    i = l;    j = r;    关键在这里,没有加if(i < j),因为下面有递归。    while(i < j)    {        while(i < j && a[j] >= x)            j--;        if(i < j)            a[i++] = a[j];        while(i < j && a[i] <= x)            i++;        if(i < j)            a[j--] = a[i];    }    a[i] = x;    Quick_sort_test(a,l,i - 1);    Quick_sort_test(a,i + 1,r);}*//**正确写法:在外层加套*/void quick_sort(int a[],int left,int right)/*注意l,r是下标*/{    int i,j,x;    i = left;    j = right;    x = a[left];    if(i < j)               /**这里一定要加外层的i < j,这是为了防止在 i=j 时,还继续执行循环代码下面的递归*/    {        while(i < j)        {            while(i < j && a[j] >= x)                j--;            if(i < j)                a[i++] = a[j];            while(i < j && a[i] <= x)                i++;            if(i < j)                a[j--] = a[i];        }        a[i] = x;        quick_sort(a,left,i-1);        quick_sort(a,i+1,right);    }}/**下面加这个函数,为了统一。在传递参数时,只用传 a[] 和 n*/void Quick_sort(int a[],int n){    quick_sort(a,0,n-1);}

/点滴积累,我的一小步O(∩_∩)O~/

0 0