快排,递归,非递归,三向切分,去掉边界条件

快排

快排的思想

典型的分治,将数组分成两个子数组,并且分别对子数组排序,且子数组的排序也是分治。

快排和归并排序的是互补的(算法4):都是分而治之的思想,快排是当两个子数组都有序的时候,数组自然就有序了,归并则是对两个已排序的数组再排序,之后有序。

java版本

算法四中:

private static void sort(Comparable[] a , int lo , int hi){
    if( hi <= lo ) {
          return;
      }
    int j = partition( a , lo ,hi);
    sort(a , lo , j - 1);
    sort(a , j + 1, hi);
}

private static int partition(Comparable[] a , int lo , int hi){

    int i = lo;
    int j = hi + 1;
    Comparable v = a[lo];
    while(true){
        //扫描左边,若结束,则当前元素大于或者等于了切分元素,或者已经到了边界
        while(less(a[++i], v)){
            if (i == hi){
                break;
            }
        }
        //扫描右边,若结束,则当前元素小于或者等于了切分元素,或者已经到了边界
        while(less(v, a[--j])){
            if (j == lo){
                break;
            }
        }        
        //运行到这里 如果i == j 则说明 a[i] 也就是a[j]  等于切分元素
        //如果i > j ,说明a[i] > 元素v  a[j] < v
        //两种情况下,直接跳出循环,并且置换lo j 的元素,不然就是还没相遇,那么交换i j
        if(i >= j){
            break;
        }
        exch(a , i , j);
    }
    exch(a , lo , j);
    return j;
}

private static int partition2(Comparable[] a , int lo , int hi){
    Comparable v = a[lo];
      int i = lo;
     int j = hi;
    while(i < j) {
        while(i < j && a[j] >= v) --j;
        a[i] = a[j]; // 找到一个小于的, 或者就是i
        while(i < j && a[i] <=  v) ++i;
        a[j] = a[i];
    }
   a[i] = v
  return v;
}

python版本
def sort_quick(arr,low,high):
    if low >= high :
        return
    j = sort_partition(arr,low,high)
    sort_quick(arr,low,j-1)
    sort_quick(arr,j+1,high)



def sort_partition(arr,low,high):
    l = low
    h = high
    comparable = arr[low]
    while l < h:
        #将列表中 小于等于枢轴值的,移动到左边
       # 下面的两个while中大小判断加 = 都是可以通过的 
        while arr[h] > comparable and l < h:
            h -= 1
        # 跳出此循环,则arr[h] <= compareble,
        arr[l] = arr[h]
      #将列表中 大于枢轴值的,移动到右边
        while arr[l] <= comparable and l < h:
            l += 1
         # 跳出此循环,则arr[l] > compareble,
        arr[h] = arr[l]
    # 跳出此循环,则 l >= h, 其实是l == h ,
    # 下面的是arr[l]  return l 也可以 
    if(l > h):
        print("l > h")
    arr[h] = comparable
    return h

去掉边界检查 算法4课后2.3.18

在排序前,便利一遍数组,找到最大值,并将最后一个与它交换,即保证最后一个元素最大,并且不再改变值,成为哨兵。于是numbers[++i] < key第一次可能会在最后一个最大元素处跳出循环,之后会以右子数组的最左,或者是partition的返回值作为哨兵。


class Solution {
    public int[] sortArray(int[] nums) {
// 最后一个元素最大
        int maximum = -1;
        int index = -1;
        for(int i  = 0 ; i < nums.length ;  ++ i){
            if(nums[i] >= maximum){
                maximum = nums[i];
                index = i;
            }
        }
        nums[index] = nums[nums.length - 1];
        nums[nums.length - 1] = maximum;
        
        sort(nums, 0 , nums.length - 1);
        return nums;
        
    }
    
    private void  sort(int [] numbers, int lo, int hi){
        if(hi <= lo) return;
        int p = partition2(numbers, lo , hi);
        sort(numbers, lo, p -1);
        sort(numbers, p + 1, hi);

    }
    
    int partition2(int[] numbers, int lo , int hi){
        int key = numbers[lo];
        int i = lo;
        int j = hi + 1;

        while(true){
// 去掉边界检查
            while(numbers[ ++ i] < key);
            while(numbers[ -- j] > key);

            if(i >= j){
                break;
            }
            int exchange = numbers[j];
            numbers[j] = numbers[i];
            numbers[i] = exchange;
        }
        numbers[lo] = numbers[j];
        numbers[j] = key;
        return j;
    }

}

优化

  1. 将数组打乱
    排序的性能还取决于数组的有序度,快排是第一批偏爱随机性的算法,若有序,则最坏的复杂度是n^2
  2. 考虑切分元素有重复
    考虑在遇到和枢轴值一样大小的元素的时候停下,避免不必要的交换

改进

  1. 切换插入
    对于小数组,插入排序比快排更快,建议在hi - lo <= M的时候切换为插入排序,M通常去5~15。
  2. 三取样切分
    去数组中随机三个数的中位数作为枢轴值
  3. 三向切分
    在数组中含有大量重复数值的情况下,普通快排会多很多次交换,而采用三向切分使得包含大量重复数值的快排更快,但是降低了包含不多重复值的数组的排序速度。然而,J和D找到了更优的三向方法,可以比归并和其他排序算法更快排序包含大量重复元素的数组。
    三向切分思想
      维护指针 lt,i,gt :
    a[ lo...lt - 1 ] 存放比枢轴值小的数
    a[ lt...i - 1 ] 存放和枢轴值一样大
    a[ i...gt ] 存放还未比较大小的数
    a[ gt + 1...hi ] 存放比枢轴值大的数

java版


private static void sort(Comparable[] a , int lo, int hi){
    if(hi <= lo){
        return;
    }
    int lt = lo, i = lo + 1, gt = hi;
    Comparable v = a[lo];
    while(i <= gt){
        int cmp = a[i].compareTo(v)
        if (cmp > 0){
            exch(a , i , gt--);
        }else if (com < 0){
            exch(a , lt++ , i++);
        }else{
            i ++;
        }

    }
    sort( a , lo , lt -1);
    sort(a , gt + 1 , hi);
}

python版

def  three_quick_sort(nums,lo,hi):
    #维持三个指针 lt i gt
    if lo >= hi:
        return
    lt = lo
    i = lo + 1
    gt = hi
    v = nums[lo]
    # [lo,lt - 1] 小于v
    # [lt,i - 1] 等于v
    # [i, gt] 未知
    # [gt + 1 , hi] 大于
    while( i <= gt):
        # i位置大于v,把它移到后面,和gt位置交换 此时的gt是未知大小
        if  v < nums[i]:
            nums[i],nums[gt] = nums[gt], nums[i]
            #这个时候 gt 上是大于v的值,i不知,所以i不能更新
            gt -= 1
        # i 位置的值小于等于 v
        elif  nums[i] < v:
            # 将 i,lt位置上的值调换
            nums[i] , nums[lt] = nums[lt] ,nums[i]
            #此时 i ,lt位置上的值都是确定大小的
            lt += 1
            i +=1
        else:
        # 最后一种可能,i位置的值 == v
            i += 1
    three_quick_sort(nums,lo,lt - 1)
    three_quick_sort(nums , gt + 1 ,hi)

a = [5,2,6,9,3,1,3,2,4,5,6,8,9]
three_quick_sort(a,0,len(a) - 1)
print(a)

不要忘记了跳出递归的条件

if lo >= hi:
        return

对于三向切分的优化

非递归的快排思想和实现

所有的递归程序 == 循环 + 栈
将递归的调用栈保存到栈中,保存的是数组的元素的下标:low 和 high,且相互对应,既可以头(low)尾(high)呼应,也可以一次弹出两个分别是low和high。

具体实现如下:

应该用stack实现保存的下标,一次放两个,一次弹出两个,我只是喜欢将它们分别放在前后

java版

class Solution {

    private void  nonCurSort(int [] numbers){
        if(numbers.length <= 1) return;
        ArrayList<Integer> arr = new ArrayList<>();
        arr.add(0);
        arr.add(numbers.length - 1);

        while( !arr.isEmpty()){

            int lo = arr.get(0);
            arr.remove(0);
            int hi = arr.get(arr.size() - 1);
            arr.remove(arr.size() - 1);
            int mid = partition2(numbers, lo , hi);
            if(mid - 1 > lo){
                arr.add(0, lo );
                arr.add(mid - 1);

            }
            if(mid + 1 < hi){
                arr.add(0, mid + 1);
                arr.add(hi);
            }
        }

    }
    

// partition函数一样
        private int partition(int[] numbers , int low , int high){
        int i = low;
        int j = high + 1;
        int key = numbers[low];
        while(true){
            while(numbers[++ i] < key){
                if( i == high){
                    break;
                }
            }
            while(numbers[ -- j] > key){
                if( j == low){
                    break;
                }
            }
            if( i >= j){
                if( i== j){
                    System.out.println(i + " " + numbers[i] );
                }
                break;
            }
            int exchange = numbers[j];
            numbers[j] = numbers[i];
            numbers[i] = exchange;
        }
        numbers[low] = numbers[j];
        numbers[j] = key;
        return j;
}


python版

'''
非递归的快排的实现
'''
import sys
def quick_sort(length, nums):
    stack = []
    stack.append(0)
    stack.append(length - 1)
    while(len(stack) != 0):
        hi = stack.pop(-1)
        lo = stack.pop(0)
        mid = sort_partition(nums,lo,hi)
# 将左边子数组放入待排
        if mid - 1 > lo:
            stack.insert(0,lo)
            stack.append(mid - 1)
# 将右边子数组放入待排
        if mid + 1 < hi: 
            stack.insert(0,mid + 1)
            stack.append(hi)
    return nums

def sort_partition(arr,low,high):
    l = low
    h = high
    comparable = arr[low]
    while l < h:
       #将列表中 小于等于枢轴值的,移动到左边
       #下面的两个while中大小判断加 = 都是可以通过的 
        while arr[h] > comparable and l < h:
            h -= 1
        # 跳出此循环,则arr[h] <= compareble,
        arr[l] = arr[h]
      #将列表中 大于枢轴值的,移动到右边
        while arr[l] <= comparable and l < h:
            l += 1
         # 跳出此循环,则arr[l] > compareble,
        arr[h] = arr[l]
    # 跳出此循环,则 l >= h, 
    # 下面的是arr[l]  return l 也可以 其实是l == h 
#    if(l > h):
 #       print("l > h")
    arr[h] = comparable
    return h



if __name__ == "__main__":
    for x in sys.stdin:
        tmp = [int(i) for i in x.strip().split(" ")]
        tmp = quick_sort(tmp[0], tmp[1:])
        ans = ""
        for i in tmp:
            ans = ans + str(i) + " "
        print(ans.strip())

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容