冒泡排序与快速排序

本文详细介绍了快速排序算法的原理,包括冒泡排序和交换排序的基础知识。重点讲解了快速排序的两种分区方法——挖坑法和交换法,并提供了相应的代码实现。此外,还讨论了非递归实现快速排序的栈方法。最后,文章提到了基准元素选择不当可能导致的时间复杂度退化问题。
摘要由CSDN通过智能技术生成
1.冒泡排序

待补充

2.交换排序

同冒泡排序一样,快速排序也属于交换排序,通过元素之间的比较和交换位置来达到排序的目的。

冒泡排序一轮只把一个元素移动到数列的一端;而快速排序在每一轮挑选一个基准元素,并让其它比它小的元素在数列一边,比它大的元素在数列的另一边,从而将数列分解成了两部分。
在这里插入图片描述
交换排序是分治法的一种求解思路,每一轮将元素分成两段,直到不可分为止,平均情况下需要logN轮,因此快排的平均时间复杂度是O(NlogN)
在这里插入图片描述
依据这个思想

void quickSort(int arr[], int startIndex, int endIndex){
    //递归结束条件
    if(startIndex >= endIndex){
        return;
    }
    //计算基准元素位置
    int pivotIndex = partition(arr,startIndex,endIndex);
    //用分治法递归数列两部分
    quickSort(arr,startIndex,pivotIndex - 1);
    quickSort(arr,pivotIndex + 1, endIndex);
}

现在问题转换成用什么方法找出pivotIndex?

方法一:挖坑法

给定原数列,要求从小到达排序:
在这里插入图片描述
第一步:选择基准元素pivot,记住基准元素index,设置left和right分别位于数列的两个端点。
在这里插入图片描述

第二步(循环体):从right开始向左遍历数列,一旦遇到比基准数字的数字就将这个数字“填入”“坑”(第一次“坑”是基准元素所在的位置)。同时left向右移动一位。

当前数列中,1<4,把1填入“坑”的位置。此时,元素1的位置变成了新的“坑”。同时,left向右移动一位。
在这里插入图片描述
接下来,切换到left指针进行比较。从left开始向右遍历数列,一旦遇到比基数字的数字就将这个数字填入“坑”。同时,right向左移动一位。
在这里插入图片描述此时,right右边橙色的区域代表着大于基准元素的区域,left左边黄色区域代表着小于基准元素的区域。

跳出循环的条件:left等于right的时候,此时数列左边的元素都小于4,数列右边的元素都大于4,这一轮交换终告结束。
在这里插入图片描述
代码实现:

int partition(int arr[], int startIndex, int endIndex){
    //取第一个位置的元素作为基准元素
    int pivot = arr[startIndex];
    int left = startIndex;
    int right = endIndex;
    //"坑"的位置,初始等于pivot的位置
    int index = startIndex;
    //大循环在左右指针重合或者交错时候结束
    while (left <= right){
        //right指针从右往左进行比较
        while(left <= right ){
            if(arr[right] < pivot){
                arr[left] = arr[right];
                index = right;//更新“坑”的位置
                left++;
                break;
            }
            right--;
        }
        //left指针从左往右比较
        while (left <= right){
            if(arr[left] > pivot){
                arr[right] = arr[left];
                index = left;
                right--;
                break;
            }
            left++;
        }
    }
    arr[index] = pivot;
    return index;
}

方法二:交换法

仍然沿用第一个例子,给定数列,从小到大排序:
在这里插入图片描述
第一步:同挖坑法基本一样。选定基准元素pivot,设置left和right分别位于数列的两个端点。这里不用“坑”,也就不用记住基准元素的位置。
在这里插入图片描述
第二步(循环体):从right指针开始向左遍历,一旦遇到小于pivot的元素,则停止遍历,切换到left指针。【当前数列中,1<4,right直接停止遍历,切换到left指针。】

轮换到left指针移动,向右遍历,一旦遇到大于pivot的元素,则停止遍历。【left一开始指向的是基准元素,判断肯定满足条件,left向右一位,7>4,left指针在这里停下。】

令left和right指向的元素进行交换。
在这里插入图片描述
循环结束的条件?
在这里插入图片描述
第三步:退出循环后,令pivot元素和【left与right重合点元素】交换。此时,数列左边都小于4,右边都大于4,这一轮结束。
在这里插入图片描述
代码实现:

int partition(int arr[], int startIndex, int endIndex){
    //取第一个元素作为基准元素
    int pivot = arr[startIndex];
    int left = startIndex;
    int right = endIndex;
    while (left != right){
        //控制right指针比较并左移动
        while (left < right && arr[right] > pivot){
            right--;
        }
        //控制left指针比较并右移动
        while (left < right && arr[left] <= pivot){
            left++;
        }
        if(left < right){
            int p = arr[left];
            arr[left] = arr[right];
            arr[right] = p;
        }
    }
    int p = arr[left];
    arr[left] = arr[startIndex];
    arr[startIndex] = p;
    return left;
}

和挖“坑”法相比,交换法在partition函数中进行的元素交换次数更少。

基准元素选择第一个出现的问题:一个逆序序列,期望你顺序排序。这种情况下无法体现分治法的优势,时间复杂度退化成O(N2).

能否用非递归实现快速排序呢?
 答案是可以的.绝大多数用递归来实现的问题,都可以用的方式来代替。
 因为代码中一层一层的方法调用,本身就是一个函数栈。每次进入一个新函数,就相当于入栈;每次有函数返回,就相当于出栈。

非递归实现

可以把原本的递归实现转化成一个栈的实现,在栈当中存储每一次函数调用的参数:
在这里插入图片描述
代码实现:

void quickSort(int arr[], int startIndex, int endIndex){
    stack<int> stk;
    if(startIndex < endIndex){
        int pivot = partition(arr,startIndex,endIndex);

        if(pivot - 1 > startIndex){
            stk.push(startIndex);
            stk.push(pivot - 1);
        }

        if(pivot + 1 < endIndex){
            stk.push(pivot + 1);
            stk.push(endIndex);
        }
        while (!stk.empty()){
            int right = stk.top();//保存的某区间的右端点
            stk.pop();
            int left = stk.top();//保存的某区间的左端点
            stk.pop();

            pivot = partition(arr,left,right);
            if(pivot - 1 > left){
                stk.push(left);
                stk.push(pivot - 1);
            }

            if(pivot + 1 < right){
                stk.push(pivot + 1);
                stk.push(right);
            }
        }
    }
}

函数引入了一个存储int类型元素的栈,用于存储每一次交换时的起始下标和结束下标。

每一次循环,都会让栈顶元素出栈,进行排序,并且按照基准元素的位置分成左右两部分,左右两部分再分别入栈。当栈为空时,说明排序已经完毕,退出循环。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值