快速排序思想及优化



    1)挖坑填数(l标记头,h标记尾):找基准(最中间的数)

从数列中取出一个基准(一般取第一个);h从后往前遍历,找到比基准小的数字,将此数字填入之前取基准的坑中(即l的位置);l再从头往后遍历,找到比基准大的数字,填入之前填基准挪走数字的坑中(即h的位置)。

重复上述操作,直到l,h相遇(注意:小心l,h越过去),相遇位置即基准要放入的位置。此刻基准左边都比基准小,基准右边都比基准大。(即基准已经到了最终该放的位置)

    2)分治法:(只要数据量 >= 2 ,就继续划分)

对之前基准所到位置的左边和右边分别重复(1)的操作。

2.代码实现:

static int Datum(int *arr,int low,int high)//一趟划分过程//返回基准下标

{

int index;

if(arr == NULL || low >= high) index = -1;

else

{

int tmp = arr[low];//基准

while(low < high)

{

//这里判断的原因是:怕low和high越过去

while((low < high) && tmp <= arr[high]) -- high;

if(low == high) break;//从while循环出来,两种情况:1.low == high 2.low < high

else

{

arr[low] = arr[high];//把小的换到前面来

}

 

while((low < high) && tmp >= arr[low]) ++low;

if(low == high) break;

else

{

arr[high] = arr[low];//不稳定,如果有重复的数字这里判断不出来

}

}

arr[low] = tmp;

index = low;

}

return index;

}

 

//时间递归深度 O(logn)  //空间 O(logn)

static void Quick(int *arr,int low,int high)

{

int index = Datum(arr,low,high);

if(low+1 < index)//左边//保证至少还有两个数才再次调用快排

{

Quick(arr,low,index-1);

}

if(index < high-1)//右边

{

Quick(arr,index+1,high);

}

}

 

void Q_sort(int *arr,int n)

{

Quick(arr,0,n-1);//递归调用时,接口和实际调用有差距,重新实现一个子函数

}

3.时间复杂度:O(nlogn)

4.空间复杂度:O(logn)~O(n)   最差O(n)(基本有序的情况)

5.稳定性:不稳定

6.快排的时间复杂度的优化:

(1.最开始的时候找到之后交换,现在只是先挖坑再填数而不是交换,降低了多余或无用交换//

2.选取基准:(1)选取第一个(如果数组已经有序,分割不理想,退化成冒泡);(2)随机选取基准(相对安全,仍然会出现最坏的,整个数组数字全相等);(3)三数取中//

3.当待排序序列的长度分割到一定大小,使用插入排序(对于很小和部分有序的数组,快排不如插排好)//

4.在一次分割后,把key相等的元素聚集在一起,下次再分割,无需对key再分割(三数取中选择枢轴+插排+聚集相等元素的组合,效果好的出奇)//

5.优化递归(尾部的两次递归,可以对其尾递归优化,优点:减少递归的深度,O(n)->O(logn),提高性能)//

总结:这里效率最好的快排组合 是:三数取中+插排+聚集相等元素,它和STL中的Sort函数效率差不多)

  1)思想:(这里只做最优化的一种)三数取中+插排+聚集

   //三数取中:要将待排序的序列划分成等长的子序列,基准得是中值,一般的做法是使用左端、右端和中心位置上的三个元素的中值作为基准

   //插排:直接插入排序

   //聚集:一次分割后,把key相等的元素聚在一起,继续下次分割时,不用再对与key相等的元素进行分割;两步:1.在划分过程中,把与key相等的元素放到数组的两端;2.划分结束后,把与key相等的元素移到基准周围

  2)代码实现:

//三数取中

int Datum(int *arr,int low,int high)

{

int mid = low + ((high - low) >> 1);//计算数组中间的元素的下标

if(arr[mid] > arr[high])//目标:arr[mid] <= arr[high]

{

swap(arr[mid],arr[high]);

}

if(arr[low] > arr[high])//目标:arr[low] <= arr[high]

{

swap(arr[low],arr[high]);

}

if(arr[mid] > arr[low])//目标:arr[low] >= arr[mid]

{

swap(arr[mid],arr[low]);

}

//此时,arr[mid] <= arr[low] <= arr[high]

return arr[low];

//low的位置上保存这三个位置中间的值

//分割时可以直接使用low位置的元素作为基准,而不用改变分割函数了

}

 

//直接插入

void Insert_sort(int *a, int low,int high)

{

    int i, j, k;

 

    for (i = low; i < high; i++)

    {

        //为a[i]在前面的a[0...i-1]有序区间中找一个合适的位置

        for (j = i - 1; j >= 0; j--)

            if (a[j] < a[i])

                break;

 

        //如找到了一个合适的位置

        if (j != i - 1)

        {

            //将比a[i]大的数据向后移

            int temp = a[i];

            for (k = i - 1; k > j; k--)

                a[k + 1] = a[k];

            //将a[i]放到正确位置上

            a[k + 1] = temp;

        }

    }

}

 

//改进后的快排

void Q_sort1(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)//数量过小就用直接插入

{

Insert_sort(arr,low,high);

return ;

}

 

//一次分割

int key = Datum(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;

}

Q_sort1(arr,first,low-1-leftlen);

Q_sort1(arr,low+1+rightlen,last);

}

 

//对外接口

void Q_sort1(int *arr,int n)

{

Q_sort1(arr,0,n-1);

}

 

猜你喜欢

转载自blog.csdn.net/YanJing1314/article/details/80008006