其中,每一遍排序的间隔在希尔排序中被称之为增量,所有的增量组成的序列称之为增量序列, 上图中增量序列为 [5, 2, 1]。增量依次递减,最后一个增量必须为 1。
有一条非常重要的性质保证了希尔排序的效率:D(K+1)间隔有序的序列在经过D(K)间隔排序后仍然是D(K+1)间隔有序的,其中D(K+1) >= D(K)
void shellSort(vector<int>& arr){
//排序间隔循环
for(int gap = arr.size() / 2; gap > 0; gap /= 2){
/-------以下代码其实就是插入排序-----------/
//使用当前间隔进行排序,元素范围为`[gap, size()-1)
for(int i = gap; i < arr.size(); i++){
//记录当前待插入元素 和 第一个被比较的元素
int curNum = arr[i];
int j = i - gap;
//如果没有比较到首元素 或 带插入元素仍小于当前比较元素,继续比较
while(j >= 0 && arr[j] > curNum){
arr[j + gap] = arr[j];
j -= gap;
}
//找到了插入位置j+gap(这里加gap是抵消最后一次-gap)
arr[j + gap] = curNum;
}
}
}
平均时间复杂度为O(n^1.5)
最坏时间复杂度O(n^1.5),平均时间复杂度O(n^1.25)
最坏时间复杂度O(n^(4/3)),平均时间复杂度O(n^(7/6))
当我们从小到大排序时,在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对
对于随机数组,逆序对的数量是 O(n^2)级的,如果采用「交换相邻元素」的办法来消除逆序对,每次最多只能消除一组逆序对,因此必须执行 O(n^2)级的交换次数,这就是为什么冒泡、插入、选择算法只能到 O(n^2)级的原因。反过来说,基于交换元素的排序算法要想突破 O(n^2) 级,必须通过一些比较,交换间隔比较远的元素,使得一次交换能消除一个以上的逆序对。
堆是符合以下两个条件之一的完全二叉树:
对于按照层次遍历记录成数组的完全二叉树,将根节点的下标记录为0,则有以下性质:
//调整数组中的index = i的元素,使该元素的位置满足最大堆,其中heapSize是当前需要考虑的最大堆的元素数量
void maxHeapAdjustI(vector<int>& arr, int i, int heapSize){
//获取该元素的左右孩子坐标
int left = 2 * i + 1;
int right = left + 1;
//maxIndex为该元素与其左右孩子中最大值的index
int maxIndex = i;
if(left < heapSize && arr[left] > arr[maxIndex]) maxIndex = left;
if(right < heapSize && arr[right] > arr[maxIndex]) maxIndex = right;
//如果最大值不是该元素
if(maxIndex != i){
//将该元素与最大值交换
swap(arr[i], arr[maxIndex]);
//并继续调整该元素使其满足最大堆(交换后该元素的index从i变成了maxIndex)
maxHeapAdjustI(arr, maxIndex, heapSize);
}
}
//初始化最大堆
void buildMaxHeap(vector<int>& arr){
//针对最大堆中的每个非叶子节点,调整其位置使其满足最大堆
for(int index = arr.size() / 2 - 1; index >= 0; index--){
maxHeapAdjustI(arr, index, arr.size() - 1);
}
}
//排序主函数
void heapSort(vector<int>& arr){
//初始化最大堆
buildMaxHeap(arr);
//将最大堆根元素放到数组最后面,然后重新组织最大堆
for(int i = arr.size() - 1; i > 0; i--){
//将最大堆根元素放到最后面
swap(arr[0], arr[i]);
//由于最后一个元素被交换到index=0的位置上,因此需要针对该元素重新调整最大堆
maxHeapAdjustI(arr, 0, i);
}
}
堆初始化时间O(n),之后维护堆的总时间O(nlogn),总时间复杂度O(nlogn)
按照上述规则,每轮中的基数都被调整到其最终的位置上:第一轮遍历排好 1 个基数,第二轮遍历排好 2 个基数(两个子数组中有两个基数),第三轮遍历排好 4 个基数(四个子数组中有四个基数)… 因此总遍历轮数为logn ~ n 次
通常来讲有三种选择方式:
其中选择区间内一个随机元素作为基数
平均时间复杂度是最优的:
注意:这里数组使用的是双闭区间,
/-----使用递归函数实现排序-----/
void quickSort(vector<int>& arr, int beg, int end){
//递归终止条件:当前分数组中只有1个或没有元素时,退出递归
if(beg >= end) return;
/----双指针法对进行本轮分区,left和right分别指向开始和结尾-----/
int left = beg, right = end;
/-----当指针没相遇的时候,不断寻找大于基数和小于基数的对,交换他们-----/
while(left < right){
while(left < right && arr[right] >= arr[beg]) right--; //保证指针相遇的时候及时退出,一定要先找右边小于基数的元素,这样退出的时候,left和right才会指向小于基数的元素
while(left < right && arr[left] <= arr[beg]) left++;
swap(arr[left], arr[right]); //找到这样的一对数,交换他们
}
//当指针相遇时,先找右边小于基数的元素,因此此时指针指向小于等于基数的元素
//因此交换基数和当前指针位置即完成数组分区
swap(arr[beg], arr[left]);
/-----迭代处理左数组和右数组
quickSort(arr, beg, left - 1);
quickSort(arr, left + 1, end);
}
开辟一个长度等同于两个数组长度之和的新数组,并使用两个指针来遍历原有的两个数组,不断将较小的数字添加到新数组中,并移动对应的指针即可。
vector<int> mergeSort(vector<int> arr1, vector<int> arr2){
/---算法初始化---/
vector<int> arr(arr1.size() + arr2.size()); //开辟新数组
int index1 = 0, index2 = 0; //初始化遍历下标
/---当两个有序数组均存在元素时候---/
while(index1 < arr1.size() && index2 < arr2.size()){
/---哪个数组的元素小从哪个数组中取出来一个---/
if(arr1[index1] <= arr2[index2]){
arr[index1 + index2] = arr1[index1];
index1++;
} else {
arr[index1 + index2] = arr2[index2];
index2++;
}
}
/---如果其中一个数组没了,就从剩下的数组直接补上---/
while(index1 < arr1.size()){
arr[index1 + index2] = arr1[index1];
index1++;
}
while(index2 < arr2.size()) {
arr[index1 + index2] = arr2[index2];
index2++;
}
return arr; //返回结果
}
我们可以把数组不断地拆成两份,直到只剩下一个数字时,这一个数字组成的数组我们就可以认为它是有序的,两个对于两个由一个数字组成的数组我们就可以使用归并排序得到一个有两个数字的有序数组;然后再将这些拆分的数组不断的两两组合起来,就完成了归并排序。
注意:这里数组使用的是双闭区间
/-----使用归并排序原地排序两个有序数组-----/
void merge(vector<int>& arr, int beg, int end) {
//两个数组分别是当前数组的前半部分和后半部分
int index1 = beg;
int end1 = (beg + end) / 2;
int index2 = end1 + 1;
//----原地归并排序---/
while (index1 <= end1 && index2 <= end) {
//一次查找左数组中大于右数组的元素,交换两者,让小的到左数组中
//这里是大于而不是小于等于,这是有序的关键
if (arr[index1] > arr[index2]) {
swap(arr[index1], arr[index2]);
//被交换到右数组中的较大者仍需要向后查找,知道找到>=自己的元素,坐在该元素的前面
//这里找到>=自己的元素,是算法有序的关键
if (index2 != end) {
int curNum = arr[index2];
int curIndex = index2;
while (curIndex < end && arr[curIndex + 1] < curNum) {
arr[curIndex] = arr[curIndex + 1];
curIndex++;
}
arr[curIndex] = curNum;
}
}
index1++;
}
}
/-----递归调用归并排序对数组进行排序----/
void mergeSort(vector<int>& arr, int beg, int end){
//递归终止条件:当数组中只有一个元素的时候,停止递归
if(beg == end) return;
//将当前数组分割为左右两个子数组分别排序
int middle = (beg + end) / 2;
mergeSort(arr, beg, middle);
mergeSort(arr, middle + 1, end);
//调用归并排序对两个有序数组进行排序
merge(arr, beg, end);
}