排序——交换排序

基本思想

两两比较待排序元素的大小,发现两个元素的次序相反时即进行交换,直到序列全部有序为止。

冒泡排序

1.排序思路

对无序区每两个相邻的元素进行比较,小元素换至大元素之后(之前),经过一趟排序后,最小的元素到达最后端(最前端)。接着,再在剩下的元素中重复上述过程。这样,通过无序区中相邻元素的比较和交换,可以使得最小的元素像如气泡一般逐渐往上“漂浮”。

2.排序算法

void BubbleSort(int *arr, int size)
{
    if (arr == NULL)
        return;

    int i, j;
    for (i = 0; i < size-1; i++)
    {
        //正序
        /*for (j = size - 1; j > i; j--)
        {
            if (arr[j] < arr[j - 1])
            {
                int tmp = arr[j];
                arr[j] = arr[j - 1];
                arr[j - 1] = tmp;
            }
        }*/
        //逆序
        for (j = 0; j < size - 1 - i; j++)
        {
            if (arr[j] < arr[j + 1])
            {
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
            }
        }
    }
}

3.算法分析

当初始数据为正序时,一趟扫描即可完成排序,此时最好时间复杂度为O(N);当初始数据为逆序时,则需要进行n-1趟排序,且每趟排序要进行n-i-1次比较,每次比较都会交换元素,此时最坏时间复杂度为O(N^2)。
故,冒泡排序的时间复杂度为O(N^2),空间复杂度为O(1)。虽然冒泡排序不一定要进行n-1趟,但它的元素移动次数较多,所以平均时间性能比直接插入排序要差。
冒泡排序是一种稳定的排序算法。

4.算法优化

当某趟排序之后,序列已经完全有序时,大可不必继续排序,于是我们设置一个标志为flag,在每趟排序的开始重置flag,若该趟排序结束时,flag没有变化,则证明序列已经有序,即可break退出循环。

void BubbleSort1(int *arr, int size)
{
    if (arr == NULL)
        return;

    int i, j, flag;
    for (i = 0; i < size - 1; i++)
    {
        flag = 1;
        for (j = size - 1; j>i; j--)
        {
            if (arr[j] < arr[j - 1])
            {
                int tmp = arr[j];
                arr[j] = arr[j - 1];
                arr[j - 1] = tmp;
                flag = 0;
            }
        }
        if (flag == 1) //说明此趟排序没有进入内循环,已经有序
        {
            break;
        }
    }
}

快速排序

1.排序思路

在待排序的n个元素中任取一个元素作为基准,数据序列被此元素划分成两部分,比该元素小的所有元素放在前边部分,比该元素大的所有元素放在后边部分,并把该元素排在这两部分的中间。这个过程称为一趟快速排序,之后对划分出来的两部分分别重复上述过程,直至每部分内只有一个或没有元素为止。

2.排序算法

//1.左右指针法
int _PartSort1(int *arr, int begin, int end)
{
    int key = arr[end];
    int left = begin;
    int right = end;

    while (left < right)
    {
        while (left < right && arr[left] <= key)
        {
            left++;
        }
        while (left < right&&arr[right] >= key)
        {
            right--;
        }
        swap(arr[left], arr[right]);
    }
    swap(arr[left], arr[end]);  

    return left;
}

//2.挖坑法
int _PartSort2(int *arr, int begin, int end)
{
    int key = arr[end];
    while (begin < end)
    {
        while (begin < end  && arr[begin] <= key)
        {
            begin++;
        }
        arr[end] = arr[begin];  //end当坑
        while (begin < end && arr[end] >= key)
        {
            end--;
        }
        arr[begin] = arr[end];  //begin当坑
    }
    arr[begin] = key;

    return begin;
}

//2.前后指针法
int _PartSort3(int *arr, int begin, int end)
{
    int prev = begin;
    int cur = begin+1;
    while (cur <= end)
    {
        if (arr[cur] <= arr[begin])
        {   
            prev++;
            if (prev != cur)
            {
                swap(arr[prev], arr[cur]);
            }
            cur++;
        }
        else
        {
            cur++;
        }
    }
    swap(arr[prev], arr[begin]);

    return prev;
}

void Quick(int *arr, int begin, int end)
{
    if (arr == NULL)
        return;

    if (begin < end)
    {
        //int div = _PartSort1(arr, begin, end);
        //int div = _PartSort2(arr, begin, end);
        int div = _PartSort3(arr, begin, end);

        Quick(arr, begin, div - 1);
        Quick(arr, div + 1, end);
    }
}

3.算法分析

快速排序的时间主要耗费在划分操作上,当初始数据递增有序或递减有序,且每次的划分基准都取最小值或最大值时,则快速排序所需的比较次数最多,最坏时间复杂度为O(N^2)。在最好情况下,每次划分基准值都取当前无序区的中值时,划分的左右两个子序列的长度大致相等,此时的时间复杂度可用递归树来分析,递归树的高度为lgN,而递归树每一层所需要的比较次数不超过N次,则整个排序过程的总比较次数为N*lgN,故最好时间复杂度为O(N*lgN)。
注意,快速排序的时间复杂度不看最坏情况!故快速排序的时间复杂度为O(N*lgN)。递归平均所需的栈空间为lgN,故快速排序的空间复杂度为O(lgN)。快速排序是一种不稳定的排序算法。

4.算法优化

三数取中法:为了防止所选划分基准值为序列最大值或最小值,用三数取中法来确定基准值,提高算法效率。

int GetMidIndex(int *a, int begin, int end)
{
    int mid = begin + ((end - begin) >> 1);

    if (a[begin] < a[mid])
    {
        if (a[end] > a[mid])
        {
            return mid;
        }
        else if (a[end] < a[begin])
        {
            return begin;
        }
        else
        {
            return end;
        }
    }
    else  //a[begin] >= a[mid]
    {
        if (a[end] < a[mid])
        {
            return mid;
        }
        else if (a[end] > a[begin])
        {
            return begin;
        }
        else
        {
            return end;
        }
    }
}

小区间划分:优化递归的层数,对接近有序的情况不再递归,使用插入排序。

void QuickSort(int *arr, int begin, int end)
{
    if (arr == NULL)
        return;

    if (begin < end)
    {
        if (end - begin > 10)
        {
            int div = _PartSort1(arr, begin, end);

            QuickSort(arr, begin, div - 1);
            QuickSort(arr, div + 1, end);
        }
        else
        {
            InsertSort(arr + begin, end - begin + 1);
        }
    }
}

快速排序的非递归实现

1.排序思路

把递归问题转换为非递归问题,可以借助栈来实现,我们可以把每个子区间的begin和end保存在栈中,然后成对取出,对子区间排序,接着又把划分出来的子区间的begin和end保存在栈中,重复上述过程,直至子区间的元素只剩下一个或为空。

2.排序算法

oid QuickSortNonR(int *arr, int begin, int end)
{
    if (arr == NULL)
        return;

    int left, right;
    stack<int> s;
    s.push(begin);
    s.push(end);
    while (!s.empty())
    {
        right = s.top();
        s.pop();
        left = s.top();
        s.pop();
        if (left < right)
        {
            int div = _Part1(arr, left, right);
            //前部分子区间
            s.push(left);
            s.push(div - 1);
            //后部分子区间
            s.push(div + 1);
            s.push(right);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值