• 快速排序算法及其优化


    快速排序的的基本思想:
          设数组a中存放了n个数据元素,low为数组的低端下标,high为数组的高端下标,从数组a中任取一个 元素(通常选取a[ow])作为标准,调整数组a中各个元素的位置,使排在标准元素前面的元素的关键字均小于标准元素的关键字,排在标准元素后面元素的关键字均大于或等于标准元素的关键字。一次结束后,把标准元素放在适合的位置,并且把以标准元素为中心划分了两个子数组,前面数组的数都小于标准元素,后面的数组都大于标准元素。然后分别对这两个自数组进行相同的递归快速排序。递归算法的出口是high>low

    快速排序相当与冒泡排序的升级版
           1.选择基准:在待排序列中,按照某种方式挑出一个元素,作为 "基准"(pivot)

           2.分割操作:以该基准在序列中的实际位置,把序列分成两个子序列。此时,在基准左边的元素都比该基准小,在基准右边的元素都比基准大

          3.递归地对两个序列进行快速排序,直到序列为空或者只有一个元素。

    挖坑填数,进行快速排序:
    1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。

    2.j--由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。

    3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。

    4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中


    快速排序的过程图:

    快速排序的完整代码(递归):

    #include<iostream>
    using namespace std;
    int partition(int *arr,int left,int right)
    {
    	int temp=arr[left];
    	while(left<right)//直达left和right重合的时候,才找到合适的位置
    	{
    		//先从后往前找比基准小的
    		while(left<right  &&  arr[right]>=temp)//当right的值大于temp的值的时候才执行
    		        //等号一定得写,因为可能会出现,保存的temp元素和数据中的元素一样的,不写会出现死循环的现象
    		{
    			right--;
    		}
    		arr[left]=arr[right];//当right的值小于temp的值的时候执行
    		//从前往后找,找比基准大的
    		while(left<right  && arr[left] <=temp)//当left的值小于temp的值的时候执行
    		{
    			left++;
    		}
    		arr[right]=arr[left];//当left的值大于temp的时候执行
    	}
    	arr[left]=temp;//此时的left和right在同一个位置,此时为合适的位置,把temp的值给left
    	return left;//此时返回的值是temp合适的位置,即小于它的在它的左边,大于它的在它的右边
    }
     void quick(int *arr,int left,int right)
    {
    	if(left<right)
    	{
    		int pivot=partition(arr,left,right);
    		quick(arr,left,pivot-1);
    		quick(arr,pivot+1,right);
    	}
    }
    void quick_sort(int *arr,int len)
    {
    	quick(arr,0,len-1);
    }
    int main()
    {
    	int arr[]={9,5,7,10,45,12};
    	int len=sizeof(arr)/sizeof(arr[0]);
    	quick_sort(arr,len);
    	for(int k=0;k<len;++k)
    	{
    		cout<<arr[k]<<"  ";
    	}
    	cout<<endl;
    }

    快速排序的完整代码(非递归):

    #include<stack>
    #include<iostream>
    using namespace std;
     
    int partition(int *arr,int left,int right)
    {
    	int temp=arr[left];//基准
    	while(left<right)
    	{
    		//先从后往前找比基准小的
    		while(left<right && temp<=arr[right])
    		{
    			right--;
    		}
    		arr[left]=arr[right];
    		//从前往后找比基准大的
    		while(left<right  && temp>=arr[left])
    		{
    			left++;
    		}
    		arr[right]=arr[left];
    	}
    	arr[left]=temp;
    	return left;
    }
    //其实就是用栈保存每一个待排序子串的首尾元素的下标
    void q_sort(int *arr,int left,int right )
    {
    	stack<int>  st;
    	int pos=0;
        st.push (left);
    	st.push (right);
    	while(!st.empty())
    	{
    		right=st.top();
    		st.pop();
    		left=st.top();
    		st.pop ();//5,6,8,2,9,4,1,3,45,89,65
    		pos=partition(arr,left,right);
    		if(pos+1<right)//先入基准右边的,如果基准右边只有一个元素的时候,就不用入了
    		{
    			st.push (pos+1);
    			st.push (right);
    		}
    		if(pos-1>left)//再入基准左边的,如果基准左边只有一个元素的时候,就不用入了
    		{   
    			st.push (left);
    			st.push (pos-1);
    			
    		}
    	}
    }
     
    void quick_sort(int *arr,int len)
    {
    	q_sort(arr,0,len-1);
    }
    int main()
    {
    	int arr[]={5,6,8,2,9,4,1,3,45,89,65};
    	int len=sizeof(arr)/sizeof(arr[0]);
    	quick_sort(arr,len);
    	for(int i=0;i<len;i++)
    	{
    		cout<<arr[i]<<"  ";
    	}
    	cout<<endl;
    }
    

      

    快速排序的各种优化:
    优化一:三数取中法,解决数据基本有序的(就是找到数组中最小下标,最大下标,中间下标的数字,进行比较,把中间大的数组放在最左边)

    代码:

    //*************************************************************************
    void swap(int *arr,int left,int right)
    {
      int temp;
      temp=arr[left];
      arr[left]=arr[right];
      arr[right]=temp;	
    }
    //***************************************************************************
    int partition(int *arr,int left,int right)
    {
    
      //***************************
      //e.g:9,1,5,8,3,7,4,6,2
      int m=left+(right-left)/2;//找到中间的数字的下标
      if(arr[left]>arr[right])//最左大于最右的时候,交换左右
      {
        swap(arr,left,right);
      }
      if(arr[m]>arr[right])//如果中间的>right ,交换
      {
        swap(arr,m,right);
      }
      if(arr[m]>arr[left])//如果中间的>left,交换
      {
        swap(arr,m,right);
      }
      //经过交换之后low变为3
      //****************************
      int temp=arr[left];//基准
      while(left<right)//知道left和right重合的时候,才找到合适的位置
      { //从后向前找到比小的数字
        while(left<right && arr[right]>=temp)//当right的值大于temp的值的时候才执行
        {
          right--;
        }
        arr[left]=arr[right];//当right的值小于temp的值的时候执行
        while(left<right && arr[left] <= temp)//从前往后找到比基准大的数字
        {
          left++;
        }
        arr[right]=arr[left];//当left的值大于temp的时候执行
      }
      arr[left]=temp;//此时的left和right在同一个位置,此时为合适的位置,把temp的值给left
      return left;//此时返回的值是temp合适的位置,即小于它的在它的左边,大于它的在它的右边
    }
    

      


    优化二:

    随机选取基准

    引入的原因:在待排序列是部分有序时,固定选取枢轴使快排效率底下,要缓解这种情况,就引入了随机选取枢轴

    思想:取待排序列中任意一个元素作为基准

    /*随机选择枢轴的位置,区间在low和high之间*/  
    int SelectPivotRandom(int arr[],int low,int high)  
    {  
      //产生枢轴的位置  
       srand((unsigned)time(NULL));  
      int pivotPos = rand()%(high - low) + low;   
      //把枢轴位置的元素和low位置元素互换,此时可以和普通的快排一样调用划分函数  
      swap(arr[pivotPos],arr[low]);  
       return arr[low];  
    }  

    优化三:优化小数组的交换,就是为了解决大才小用问题

    原因:对于很小和部分有序的数组,快排不如插排好。当待排序序列的长度分割到一定大小后,继续分割的效率比插入排序要差,此时可以使用插排而不是快排

    截止范围:待排序序列长度N = 10,虽然在5~20之间任一截止范围都有可能产生类似的结果,这种做法也避免了一些有害的退化情形。

    #define  max_len  10
    void quick(int *arr,int left,int right)
    {
        int length=right-left;
        if(length>max_len )
        {
            int pivot=partition(arr,left,right);
            quick(arr,left,pivot-1);
            quick(arr,pivot+1,right);
        }
        else
        {
        //用插入排序
        }
    }
    

    优化四:

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

    举例:

    待排序序列 1 4 6 7 6 6 7 6 8 6

    三数取中选取枢轴:下标为4的数6

    转换后,待分割序列:6 4 6 7 1 6 7 6 8 6

                 枢轴key:6

    本次划分后,未对与key元素相等处理的结果:1 4 6 6 7 6 7 6 8 6

    下次的两个子序列为:1 4 6 和 7 6 7 6 8 6

    本次划分后,对与key元素相等处理的结果:1 4 6 6 6 6 6 7 8 7

    下次的两个子序列为:1 4 和 7 8 7

    经过对比,我们可以看出,在一次划分后,把与key相等的元素聚在一起,能减少迭代次数,效率会提高不少

    具体过程:在处理过程中,会有两个步骤

    第一步,在划分过程中,把与key相等元素放入数组的两端

    第二步,划分结束后,把与key相等的元素移到枢轴周围

    举例:

    待排序序列 1 4 6 7 6 6 7 6 8 6

    三数取中选取枢轴:下标为4的数6

    转换后,待分割序列:6 4 6 7 1 6 7 6 8 6

                 枢轴key:6

    第一步,在划分过程中,把与key相等元素放入数组的两端 

    结果为:6 4 1 6(枢轴) 7 8 7 6 6 6

    此时,与6相等的元素全放入在两端了

    第二步,划分结束后,把与key相等的元素移到枢轴周围

    结果为:1 4 6 6(枢轴)  6 6 6 7 8 7

    此时,与6相等的元素全移到枢轴周围了

    之后,在1 4 和 7 8 7两个子序列进行快排

    代码:

    void QSort(int arr[],int low,int high)  
    {  
        int first = low;  
       int last = high;  
      
       int left = low;  
        int right = high;  
     
       int leftLen = 0;  //用来统计左边与key相等的元素的个数
       int rightLen = 0;  //统计右边与key相等的元素的个数
      
        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]); //把右边与key元素相等的聚集的右端
                    right--;  
                    rightLen++;  
                }  
                high--;  
            }  
            arr[low] = arr[high];  
            while(high > low && arr[low] <= key)  
           {  
                if (arr[low] == key)  //把左边与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]);  //此时,把数组左端与key相等的数据换到key的左边
            i--;  
            j++;  
        }  
        i = low + 1;  //轴的右边
        j = last;  
        while(j > right && arr[i] != key)  
        {  
           swap(arr[i],arr[j]);  //此时,把数组右端与key相等的数据换到,key右边
           i++;  
           j--;  
       }  
        partition(arr,first,low - 1 - leftLen);  
        partition(arr,low + 1 + rightLen,last);  
    }  
  • 相关阅读:
    [大数据相关] Hive中的全排序:order by,sort by, distribute by
    [mysql] Mysql数据分组GROUP BY 和HAVING,与WHERE组合使用
    【hadoop环境问题】namenode无法启动问题解决
    【java】Java.math.BigDecimal.subtract()方法实例
    利用 force index优化sql语句性能
    navicat连接远程数据库报错'client does not support authentication protocol requested by server consider ...'解决方案
    spark SQL学习(数据源之parquet)
    spark SQL学习(load和save操作)
    spark学习(RDD案例实战)
    spark学习12(Wordcount程序之spark-shell)
  • 原文地址:https://www.cnblogs.com/still-smile/p/11547248.html
Copyright © 2020-2023  润新知