插入排序是指在待排序的元素中,假设前面n-1(其中n>=2)个数已经是排好顺序的,现将第n个数插到前面已经排好的序列中,然后找到合适自己的位置,使得插入第n个数的这个序列也是排好顺序的。按照此法对所有元素进行插入,直到整个序列排为有序的过程,称为插入排序。
(1)将待排序序列的第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
(2)从头到尾依次扫描未排序的序列,将扫描到的每个元素插入有序序列的适当位置(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面)。
C++实现代码:
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
insertSort(nums);
return nums;
}
void insertSort(vector<int>& nums) {
for (auto i=1; i<nums.size(); ++i) {
auto cur = nums[i];
auto j = i-1;
while ( j >= 0 && nums[j] > cur) {
nums[j+1] = nums[j];
--j;
}
nums[j+1] = cur;
}
}
};
插入排序是稳定排序算法。
折半插入排序(binary insertion sort)是对插入排序算法的一种改进,由于排序算法过程中,就是不断的依次将元素插入前面已排好序的序列中。由于前半部分为已排好序的数列,这样我们不用按顺序依次寻找插入点,可以采用折半查找的方法来加快寻找插入点的速度。
折半插入排序是对直接插入排序的一种简单改进。
直接插入的算法步骤是:当第i-1趟需要将第i个元素插入前面的 0 ~ i-1 个元素序列中时,总是需要从 i-1 个元素开始,逐个比较每个元素,直到找到它的位置。这样就没有利用到前面 0 ~ i-1 个元素已经有序的这个特性,所以折半插入排序就对这个问题进行了改进。
对于折半插入排序而言,当需要插入第 i 个元素时,它不会逐个进行比较每个元素,而是:
(1)计算 0 ~ i-1 索引的中间点,也就是用 i 索引处的元素和 (0 + i-1) / 2 索引处的元素进行比较,如果 i 索引处的元素值大,就直接在 (0 + i-1)/2 ~ i-1 半个范围内进行搜索;反之在0~ (0+i-1)/2 这半个范围内进行搜索,即折半;
(2)在一半的范围内搜索时,按照(1)的方法不断进行折半查找,这样就可以将搜索范围缩小到1/2、1/4、1/8…,从而快速的确定插入位置。
C++代码实现:
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
binInsertSort(nums);
return nums;
}
int binSearch(vector<int> &nums, int target, int low, int high) {
while ( low <= high) {
auto mid = low + (high - low) / 2;
if ( nums[mid] > target ) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return low;
}
void binInsertSort(vector<int> &nums) {
for (auto i=1; i < nums.size(); ++i) {
auto cur = nums[i];
// search insert place
auto j = binSearch(nums, cur, 0, i-1);
// move elements forward
for (auto k=i; k > j; --k) {
nums[k] = nums[k-1];
}
nums[j] = cur;
}
}
};
折半插入排序减少了关键字的比较次数,但是记录的移动次数不变,其时间复杂度与直接插入排序相同。
折半插入排序是稳定排序算法。
(1)希尔排序(shell sort)这个排序方法又称为缩小增量排序,是1959年D·L·Shell提出来的。该方法的基本思想是:设待排序元素序列有n个元素,首先取一个整数increment(小于n)作为间隔将全部元素分为increment个子序列,所有距离为increment的元素放在同一个子序列中,在每一个子序列中分别实行直接插入排序。然后缩小间隔increment,重复上述子序列划分和排序工作。直到最后取increment=1,将所有元素放在同一个子序列中排序为止。
(2)由于开始时,increment的取值较大,每个子序列中的元素较少,排序速度较快,到排序后期increment取值逐渐变小,子序列中元素个数逐渐增多,但由于前面工作的基础,大多数元素已经基本有序,所以排序速度仍然很快。
(3)增量increment的取法有各种方案。最初shell提出取increment=n/2向下取整,increment=increment/2向下取整,直到increment=1。但由于直到最后一步,在奇数位置的元素才会与偶数位置的元素进行比较,这样使用这个序列的效率会很低。后来Knuth提出取increment=n/3向下取整+1。还有人提出都取奇数为好,也有人提出increment互质为好。应用不同的序列会使希尔排序算法的性能有很大的差异。
选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
按增量序列个数 k,对序列进行 k 趟排序;
每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
C++代码实现:
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
shellSort(nums);
return nums;
}
void shellSort(vector<int>& nums) {
int increment = nums.size();
do {
increment = increment / 3 + 1; // decrease increment
for (int i=increment; i < nums.size(); ++i) {
auto cur = nums[i];
auto j = i - increment;
while ( j >= 0 && nums[j] > cur ) {
nums[j+increment] = nums[j];
j -= increment;
}
nums[j+increment] = cur;
}
} while (increment > 1);
}
};
希尔排序算法是不稳定算法。因为相同大小的元素会划分到不同子序列,有的子序列中该元素会调整,有的可能不调整,此时相对顺序发生变换,所以希尔排序算法是不稳定的算法。