快速排序
思想:
1.填坑和划分
以某个数为基准,一般选第一个数,从数组头尾两边开始遍历。
选出来的基准数会用一个变量保存,这样的话,这个基准数所在的数组的位置就相当于有个坑。
先从尾那边开始,找到比基准数小的,就放到左边的坑。放了之后左边的坑就填上了,右边就出现坑了。
然后又从头那边开始,找到比基准数大的,就放到右边的坑。这样子左边又出现了坑。
直到头尾相遇的时候,把基准数放到此时的坑的位置。
这样子数组就划分成了前后两部分。
2.递归
对前面一部分快排。
对后面一部分快排。
图解:
代码:
void quickSort(vector<int>& arr, int low, int high){ if(low < high){ int i = low; int j = high; //三值取中 一般做法的话没有这一步 int mid = low + (high-low)/2; if(arr[low] < arr[mid]){ arr[low] += arr[mid]; arr[mid] = arr[low]-arr[mid]; arr[low] -= arr[mid]; } if(arr[low] > arr[high]){ //如果此时low比high还大, 那low就是最大的, 并且此时不能将low和high交换, 因为我们不知道high和mid哪个是中值 if(arr[mid] > arr[high]){//如果mid比high大, 那mid就是中值 arr[low] += arr[mid]; arr[mid] = arr[low] - arr[mid]; arr[low] -= arr[mid]; } else{//否则high是中值 arr[low] += arr[high]; arr[high] = arr[low] - arr[high]; arr[low] -= arr[high]; } } //三值取中 一般做法的话没有这一步 int x = arr[i]; while(i < j){ while(i < j && arr[j] >= x) j--; if(i < j) arr[i++] = arr[j]; while(i < j && arr[i] < x) i++; if(i < j) arr[j--] = arr[i]; } arr[i] = x; quickSort(arr,low,i-1); quickSort(arr,i+1,high); } }
复杂度分析:
在一次划分中,因为从low和high开始,直到两边重合。所以对所有元素都遍历了一遍,时间复杂度为O(n)。
而在整个算法中,时间复杂度还与划分的趟数有关。
如果每次都恰好选到中间的数,则每次划分都能划成两个等长的子数组。此时时间复杂度为O(nlog2n)。
而如果每次都恰好选到子数组最大或最小的数,则有一个子列表时空的,那么就要产生n个子列表。所以最坏的情况下时间复杂度为O(n2)。
为了改善性能,避免出现最坏情况,可以在选取中间数的时候,对最左边,最右边以及中间的数进行比较,选择这三者中的中间数。
对于空间复杂度也一样,最好的情况下,需要所需栈的深度为O(nlog2(n+1)),最坏情况是O(n)。