数据结构 - 排序算法

稳定与非稳定排序

稳定排序:排序前后相同key值的元素之间的位置关系保持不变。
非稳定排序:排序前后相同key值的元素之间的位置关系可能发生变化。

内排序与外排序

内排序:待排序的序列完全放在内存中进行排序,适合不太大的元素序列。
外排序:排序过程中序列存放在外存储器,需要访问外存储器获得序列数据。

在本文中,默认的是升序排序。

1.交换排序

思想:两两比较元素的key值,如果是逆序则交换元素位置,直到全部元素都排好序。

主要有冒泡排序(bubble sort)和快排(quick sort)

冒泡排序

算法流程
1. 第1趟排序,从r[1]到r[n],两两比较,如果逆序,交换两个元素的位置,最后最大的元素会到达r[n]
2. 第2趟排序,从r[1]到r[n-1],两两比较,如果逆序,交换两个元素的位置,最后次大的元素会到达r[n-1]
3. 直到有序元素个数已经为n-1,一般要执行n-1趟,但是如果某一趟过程中没有发生任何交换,表明序列已经是有序的,排序算法可以提前终止。

时间复杂度
最好:序列已经有序,不需移动
最差:每次比较之后都要发生交换
平均:O(n2n2)

空间复杂度
只需一个辅助单元

稳定性
是稳定排序

代码

void bubble_sort(int nums[], int n)
{
    bool exchange = false;
    int temp;
    for (int i = n; i > 1; i--)
    {
        exchange = false;
        for (int j = 0; j < i - 1; j++)
        {
            if (nums[j] > nums[j + 1])
            {
                exchange = true;
                temp = nums[j];
                nums[j] = nums[j + 1];
                nums[j + 1] = temp;
            }
        }
        if (!exchange)//当某一趟过程中完全没有交换时,序列已经是有序的,可以提前终止循环
        {
            break;
        }
    }
}

快排(quick sort)

基本思想
以某个元素为界(称为支点),将序列分成两部分,一部分元素的key值小于等于支点key值,另一部分元素的key值大于支点key值,这个过程称为一次划分,划分之后再在两部分分别划分,直到整个序列有序。

算法流程
一次划分的流程:
1. 设置left和right,分别代表序列第一个和最后一个元素,支点的key值记为pivotkey
2. 从right往左搜索,找到第一个key值比pivotkey小的元素,和支点元素交换位置
3. 然后从left往右搜索,找到第一个key值比pivotkey大的元素,和支点元素交换位置
4. 依次重复以上步骤,直到left=right

快排流程:
1. 对整个序列进行依次划分,将序列分成两部分,一部分的key值小于支点的key值,另一部分的key值大于支点key值
2. 对这两部分分别再进行划分,分别得到两个部分
3. 一直执行,直到整个序列有序

时间复杂度
最好:理想情况下每次划分得到两个等长子序列,时间复杂度为O(nlog(n))
最差:每次划分后只能得到一个子序列,复杂度为O(n2n2)
当初始序列有序或基本有序时,快排会退化成冒泡排序,应对办法是采用“三者取中法”来得到支点,即取首尾以及中间3个元素中key值居中的元素作为支点。
平均:O(n2n2)

空间复杂度
快排是递归地,每层调用都要用栈来存放调用参数和指针,递归调用层次数与二叉树深度一致,因此理想情况下辅助空间为O(log(n)),即数的高度,最坏情况下得到一个单链数,此时空间复杂度为O(n)。

稳定性
是不稳定排序

代码

void quick_sort(int nums[], int n,int left,int right)
{
    int pivot;
    if (left < right)
    {
        pivot = partion(nums, n, left, right);
        quick_sort(nums, n, left, pivot-1);
        quick_sort(nums, n, pivot + 1, right);
    }

}

int partion(int nums[], int n, int left, int right)
{
    int pivot = nums[left];
    int i, j,temp,pivot_pos;
    i = left;
    j = right;
    pivot_pos = left;
    while (i < j)
    {
        while (i<j&&nums[j] >= pivot)
        {
            j--;
        }

        if (i < j)
        {
            temp = nums[pivot_pos];
            nums[pivot_pos] = nums[j];
            nums[j] = temp;
            pivot_pos = j;
        }


        while (i<j&&nums[i]<=pivot)
        {
            i++;
        }
        if (i < j)
        {
            temp = nums[pivot_pos];
            nums[pivot_pos] = nums[i];
            nums[i] = temp;
            pivot_pos = i;
        }
    }
    return i;
}

2.插入排序

基本插入排序

基本插入排序的思想是把未排序元素逐一插入到序列有序部分对应的位置,直到整个序列有序为止。

算法流程
1. 从序列第1个元素开始
2. 该元素向前比较,遇到比该元素大的,交换位置,遇到与该元素小的或者相等的,停止,或者达到序列第0个位置时终止。
3. 直到整个序列最后一个元素。

时间复杂度
最坏:序列刚好完全倒序,复杂度O(n2)O(n2)
最好:序列刚好有序,复杂度O(n)O(n)
平均:O(n2)O(n2)

空间复杂度
仅用一个辅助单元,空间复杂度O(1)O(1)

稳定性
是稳定排序

适用性
插入排序适合待排序序列部分有序或接近有序
易实现,当n很小时,是一种很好的排序方法,当n很大时,时间性能不好

代码

void base_insertion_sort(int nums[], int n)
{
    int temp,j;
    for (int i = 1; i < n; i++)
    {
        j = i;
        while (j > 0)
        {
            if (nums[j - 1] <= nums[j])
            {
                break;
            }
            else
            {
                temp = nums[j];
                nums[j] = nums[j-1];
                nums[j - 1] = temp;
            }
            j--;
        }
    }
}

二分插入排序

二分插入排序与基本插入排序的区别是在寻找每个待插入元素的插入位置时的方法不一样,二分插入排序在搜索插入位置时采用的是二分比较的方式。

算法流程
1. 从序列第1个元素开始,i=1…n-1
2. low=0,high=i-1,标定有序区间,若low>high,则得到插入位置,转5
3. low<=high,mid=(low+high)/2
4. 如果nums[mid]<=nums[i],low=mid+1
如果nums[mid]>nums[i],high=mid-1
转2
5. 得到插入位置high+1,nums[high+1]到high[i-1]的元素全部后移,令nums[high+1]=nums[i]

时间复杂度
仍为O(n2)O(n2)

稳定性
是稳定排序

代码

void binary_insertion_sort(int nums[], int n)
{
    int j,temp,low,high,mid;
    for (int i = 1; i < n; i++)
    {
        low = 0;
        high = i - 1;
        while (low<=high)
        {
            mid = (low+high) / 2;
            if (nums[mid]<nums[i])
            {
                low = mid + 1;
            }
            else
            {
                high = mid - 1;
            }
        }
        temp = nums[i];
        for (j = i; j >high+1; j--)
        {
            nums[j] = nums[j-1];
        }
        nums[high + 1] = temp;
    }
}

希尔排序(shell sort)

思想
因为插入排序在序列接近有序时效率比较高,所以想法是设置一个递减的步长序列,按步长进行直接插入排序,使待排序序列逐渐接近有序。

算法流程
1. 设置一个递减的步长序列,t1,t2,...tk,ti<tj,i<j,,tk=1t1,t2,...tk,ti<tj,i<j,,tk=1
2. 按照步长序列进行k趟直接插入排序,每趟排序是按照步长titi将序列分成t_i个子序列,分别对子序列进行直接插入排序。例如第一个子序列是nums[0],nums[ti],nums[2ti]…第2个子序列是nums[1],nums[ti+1],nums[2ti+1]…
3. 当进行到最后一趟,步长为1时,就是对整个序列进行直接插入排序,此时由于整个序列已经接近有序,直接插入排序的效率会高很多。

时间复杂度
介于O(n1.25)O(n1.25)O(n1.65)O(n1.65)之间

代码

void shell_sort(int nums[], int n)
{
    int increment = n;
    do
    {
        increment = increment / 3 + 1;
        for (int i = 0; i < increment; i++)
        {
            insertion_sort_interval(nums, n,i,increment);
        }
    } while (increment > 1);
}

void insertion_sort_interval(int nums[], int n,int start,int increment)
{
    int temp, j;
    for (int i = start+increment; i < n; i+=increment)
    {
        j = i;
        while (j > start)
        {
            if (nums[j - increment] <= nums[j])
            {
                break;
            }
            else
            {
                temp = nums[j];
                nums[j] = nums[j - increment];
                nums[j - increment] = temp;
            }
            j-=increment;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值