数据结构与算法——排序

5.1 排序的基本概念

排序是处理数据的一种最常见的操作,所谓排序就是将数据按某字段规律排列,所谓的字段就是数据节点的其中一个属性。比如一个班级的学生,其字段就有学号、姓名、班级、分数等等,我们既可以针对学号排序,也可以针对分数排序。

  • 稳定性与非稳定性

稳定排序:排序前后两个相等的数相对位置不变,则算法稳定 6 6

非稳定排序:排序前后两个相等的数相对位置发生了变化,则算法不稳定

  • 内排序与外排序

如果待排序数据量不大,可以一次性全部装进内存进行处理,则称为内排序,若数据量大到无法一次性全部装进内存,而需要将数据暂存外存,分批次读入内存进行处理,则称为外排序。

性能分析

不同的排序算法性能不同,详细性能数据如下表所示。

排序算法

平均 T(n)

最坏 T(n)

最好 T(n)

空间复杂度

稳定性

选择排序

O(n2)

O(n2)

O(n2)

O(1)

不稳定

插入排序

O(n2)

O(n2)

O(n)

O(1)

稳定

希尔排序

O(n1.3)

O(n2)

O(n)

O(1)

不稳定

冒泡排序

O(n2)

O(n2)

O(n)

O(1)

稳定

快速排序

O(nlog2​n)

O(n2)

O(nlog2​n)

O(nlog2​n)

不稳定

5.2 排序的种类

5.2.1 冒泡排序

首先引入两个概念:

  • 顺序:如果两个数据的位置符合排序的需要,则称它们是顺序的。
  • 逆序:如果两个数据的位置不符合排序需要,则称它们是逆序的。

冒泡排序基于这样一种简单的思路:从头到尾让每两个相邻的元素进行比较,顺序就保持位置不变,逆序就交换位置。可以预料,经过一轮比较,序列中具有“极值”的数据,将被挪至序列的末端。

假如序列中有n个数据,那么在最极端的情况下,只需要经过n−1轮的比较,则一定可以将所有的数据排序完毕。冒泡法排序的时间复杂度是O(n²)

代码实现:

//冒泡排序
void sort(int *array,int size){
    for(int i = 0; i < size - 1; i++){
        for(int j = 0; j < size - i - 1; j++){
            if(array[j] > array[j + 1]) {//找到最大的一个数
                swap(&array[j],&array[j + 1]);//交换函数
            }
        }
    }
}
5.2.2 插入排序

插入排序的思路也很简单:假设前面已经有i节点是有序的(排好顺序),那么就从第i+1个节点开始,插入到前面的i个节点的合适的位置中。由于第一个元素自身总是有序的(假设性),因此从第2个开始,不断插入前面的有序序列,直到全部排列完毕。

代码实现:

//插入排序
void InsertSort(int *array,int size){
    int tmp , j;
    for(int i = 1;i < size; i++){
        tmp = array[i];
        for(j = i - 1; j >= 0; j--){
            if(array[j] < tmp) break;
            array[j + 1] = array[j];//交换函数
        }
        array[j + 1] = tmp;
    }
}
5.2.3 选择排序

选择排序的思路非常简单,就是依次从头到尾挑选合适的元素放到前面。如果总共有n个节点,那么选择一个合适的节点需要比较n次,而总共要选择n次,因此总的时间复杂度是O(n²)

代码实现:

//选择排序
void SelectSort (int *array,int size){
    for(int i = 0; i < size; i++){
        int min = i;
        for(int j = i + 1; j < size; j++){
            if(array[j] < array[min]) min = j;
        }
        swap(&array[i],&array[min]);//交换函数
    }
}
5.2.4 快速排序

快排是一种递归思想的排序算法,先比较其他的排序算法,它需要更多内存空间,但快排的语句频度是最低的,理论上时间效率是最高的。

快速排序的基本思路是:在待排序序列中随便选取一个数据,作为所谓“支点”,然后所有其他的数据与之比较,以从小到大排序为例,那么比支点小的统统放在其左边,比支点大的统统放在其右边,全部比完之后,支点将位与两个序列的中间,这叫做一次划分(partition)。

0

一次划分之后,序列内部也许是无序的,但是序列与支点三者之间,形成了一种基本的有序状态,接下去使用相同的思路,递归地对左右两边的子序列进行排序,直到子序列的长度小于等于1为止。

代码实现:

//快速排序
void Quick_sort(int *array,int left,int right){
    int i,j;
    if(left > right){
        return;
    }

    int key = array[left];
    i = left;
    j = right;

    while(i!=j){

        while(array[j] >= key && i!=j){
            j--;
        }//从右往左找比key值小的数

        while (key >= array[i] && i!=j){
            i++;
        }//从左往右找比key值大的数

        if(i!=j){
            swap(&array[j],&array[i]);
        }//找到后交换数值
    }

    swap(&array[left],&array[j]);//i与j相遇后与key值交换

    Quick_sort(array,left,j-1);
    Quick_sort(array,j+1,right);

}

 把数组下标0作为基准值

       j先开始,j从后往前遍历找到第一个比基准值小的数,然后停下来

       i后开始,i从前往后遍历找到第一个比基准值大的数,然后停下来  

       此时有两种情况发生

           情况1:i,j停下来,但是没有相撞

                  i和j交换对应位置的数据即可

                  i和j交换对应位置的数据之后,接着j再次移动,i再次移动,移动的过程还是重复前面的操作,直到i和j想撞,把相撞位置的数据和基准值交换

           情况2:i,j停下来,相撞

                  把相撞位置的数据和基准值交换

                  经过上面一轮循环,此时基准值左边的都是比它小的数据,基准值右边

的都是比它大的数据

            接着左右两部分递归调用自己,重复上述过程

            时间复杂度为O(nlogn)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值