排序——交换排序

来源:互联网 发布:java飞机大战扇形子弹 编辑:程序博客网 时间:2024/06/08 19:14

基本思想

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

冒泡排序

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);        }    }}