常见排序算法-----快速排序

最好时间复杂度O(nlogn) 最坏时间复杂度O(N^2)

方法一:

左右指针法:

 1     /**
 2      * 双指针法,将基准点设置为最左端
 3      * 
 4      * @param arr
 5      * @param left
 6      * @param right
 7      */
 8     public static void quicklySort(int[] arr, int left, int right) {
 9 
10         if (left >= right) {
11             return;
12         }
13 
14         int base = arr[left];
15         int i = left;
16         int j = right;
17         while (i < j) {
18             while (j > i && arr[j] >= base) {
19                 j--;
20             }
21             while (j > i && arr[i] <= base) {
22                 i++;
23             }
24             if (i < j) {
25                 int temp = arr[i];
26                 arr[i] = arr[j];
27                 arr[j] = temp;
28             }
29         }
30         arr[left] = arr[i];
31         arr[i] = base;
32         quicklySort(arr, left, i - 1);
33         quicklySort(arr, i + 1, right);
34 
35     }

当基点设置在最左端时,要让右指针先移动(当基准设置在最右端时,要让左指针先移动)

S1 右指针移动找到第一个比基准小的数停止.

S2 左指针开始移动,找到第一个比基准大的数停止

S3 将左右指针所指的数进行交换

S4 继续上述步骤直到左右指针相遇,将其与基准进行交换

S5 按照基准的位置,分为左右两侧分别进行上述步骤,直到拆分到只剩一个元素 排序完毕

当基点设置在最左端时,要让右指针先移动(当基准设置在最右端时,要让左指针先移动)原因:

因为右指针最后停止的时候会和左指针发生交换,导致右指针停止的时候所指向的数要大于基准

若此时让左指针先进行移动,最后和两个指针相遇 (相遇在右指针的位置,此时右指针指向的数要大于基准)
若基准设置在最左端,会导致右指针指向的大于基准的数移动到左端,导致左端不是全部小于基准的数,排序失败

右指针先移动同理。

所以,当基准在最左端的时候,要右指针先移动,基准在最右端时,要左指针先移动。

方法二 挖坑法 (基准设在左边 右指针先走 和上面一样)

 1     // 挖坑法
 2     public static void quicklySort2(int[] arr, int left, int right) {
 3 
 4         if(left >=right){
 5             return ;
 6         }
 7         
 8         int base = arr[left];
 9         int i = left;
10         int j = right;
11 
12         while (i < j) {
13             // 右指针先走
14             while (j > i && arr[j] >= base) {
15                 j--;
16             }
17             arr[i] = arr[j];
18 
19             // 左指针再走
20             while (j > i && arr[i] <= base) {
21                 i++;
22             }
23             arr[j] = arr[i];
24         }
25         arr[j] = base;
26         quicklySort2(arr, left, j - 1);
27         quicklySort2(arr, j + 1, right);
28 
29     }

 

取基准的几种方法

详细见https://blog.csdn.net/hacker00011000/article/details/52176100

(1)固定位置  

(2)随机选取基准, 选取后 将该位置的基准与最左端进行交换,即可以用常规的快排方法进行排序

(3)三数去中法。使用最左端,最右端和中间位置的三个元素,将这个三个元素进行排序,选取中间位置的数做为基准数,将其与最左端进行交换,即可用普通的快排进行处理。

 四种优化方法

优化1:当待排序序列的长度分割到一定大小后,使用插入排序

优化2:在一次分割结束后,可以把与Key相等的元素聚在一起,继续下次分割时,不用再对与key相等元素分割

具体过程:在处理过程中,会有两个步骤 
第一步,在划分过程中,把与key相等元素放入数组的两端 
第二步,划分结束后,把与key相等的元素移到枢轴周围

 

void QSort(int arr[],int low,int high)  
{  
    int first = low;  
    int last = high;  

    int left = low;  
    int right = high;  

    int leftLen = 0;  
    int rightLen = 0;  

    if (high - low + 1 < 10)  
    {  
        InsertSort(arr,low,high);  
        return;  
    }  

    //一次分割  
    int key = SelectPivotMedianOfThree(arr,low,high);//使用三数取中法选择枢轴  

    while(low < high)  
    {  
        while(high > low && arr[high] >= key)  
        {  
            if (arr[high] == key)//处理相等元素  
            {  
                swap(arr[right],arr[high]);  
                right--;  
                rightLen++;  
            }  
            high--;  
        }  
        arr[low] = arr[high];  
        while(high > low && arr[low] <= key)  
        {  
            if (arr[low] == key)  
            {  
                swap(arr[left],arr[low]);  
                left++;  
                leftLen++;  
            }  
            low++;  
        }  
        arr[high] = arr[low];  
    }  
    arr[low] = key;  

    //一次快排结束  
    //把与枢轴key相同的元素移到枢轴最终位置周围  
    int i = low - 1;  
    int j = first;  
    while(j < left && arr[i] != key)  
    {  
        swap(arr[i],arr[j]);  
        i--;  
        j++;  
    }  
    i = low + 1;  
    j = last;  
    while(j > right && arr[i] != key)  
    {  
        swap(arr[i],arr[j]);  
        i++;  
        j--;  
    }  
    QSort(arr,first,low - 1 - leftLen);  
    QSort(arr,low + 1 + rightLen,last);  

 

优化3:优化递归操作

 

快排函数在函数尾部有两次递归操作,我们可以对其使用尾递归优化

 

优点:如果待排序的序列划分极端不平衡,递归的深度将趋近于n,而栈的大小是很有限的,每次递归调用都会耗费一定的栈空间,函数的参数越多,每次递归耗费的空间也越多。优化后,可以缩减堆栈深度,由原来的O(n)缩减为O(logn),将会提高性能。

 

优化4:使用并行或多线程处理子序列

posted on 2018-07-23 16:02  Mxxxx  阅读(137)  评论(0编辑  收藏  举报