插入排序的基本方法是:每一步将一个待排序的元素,按其排序码的大小,插入到前面已经排好序的一组元素的适当位置上去,直到元素全部插入为止。
i表示当前要排序的元素,j是i的前面一个元素。
1.复制插入的元素:
temp = list[i];
2.记录后移,查找插入的位置
for (; j >= 0 && list[j] > temp; --j)
list[j + 1] = list[j];
3.插入到正确的位置
list[j + 1] = temp;
#include
#include
using namespace std;
vector<int> insertSort(vector<int>list) {
if (list.empty())
return list;
int i, j, temp;
for (i = 1; i < list.size(); ++i) {//从第二个数开始遍历
temp = list[i];
j = i - 1;
for (; j >= 0 && list[j] > temp; --j) {
list[j + 1] = list[j];
}
list[j + 1] = temp;
}
return list;
}
int main() {
vector<int>list{ 8,2,1,7,4,9,3,6,5 };
auto res = insertSort(list);
for (auto& item : res)
cout << item << " ";
//1 2 3 4 5 6 7 8 9
}
比较的次数: ∑ i = 1 n − 1 1 = n − 1 \sum_{i=1}^{n-1}1=n-1 ∑i=1n−11=n−1;
移动的次数:0
比较的次数: ∑ i = 1 n − 1 i = ( n − 1 ) n ÷ 2 \sum_{i=1}^{n-1}i=(n-1)n\div2 ∑i=1n−1i=(n−1)n÷2;
移动的次数: ∑ i = 1 n − 1 ( i + 2 ) = ( n − 1 ) ( n + 4 ) ÷ 2 \sum_{i=1}^{n-1}(i+2)=(n-1)(n+4)\div2 ∑i=1n−1(i+2)=(n−1)(n+4)÷2
比较的次数: ∑ i = 1 n − 1 ( i + 1 ) / 2 = ( n − 1 ) ( n + 2 ) ÷ 4 \sum_{i=1}^{n-1}(i+1)/2=(n-1)(n+2)\div4 ∑i=1n−1(i+1)/2=(n−1)(n+2)÷4;
移动的次数: ∑ i = 1 n − 1 ( ( i + 1 ) / 2 + 1 ) = ( n − 1 ) ( n + 6 ) ÷ 4 \sum_{i=1}^{n-1}((i+1)/2+1)=(n-1)(n+6)\div4 ∑i=1n−1((i+1)/2+1)=(n−1)(n+6)÷4
折半插入排序的对象移动次数与直接插入排序相同,依赖于对象的初始排列。
1.减少了比较次数,但没有减少移动次数;
2.但是平均性能优于直接插入排序。
二分法所需要的比较次数与待排序对象的初始排列无关,仅仅依赖于对象个数n。在插入第i个元素时,需要经过 l o g 2 i log_2i log2i次比较,才能确定它应该插入的位置。
1.当n比较大时,用二分法比直接插入排序的最坏情况要好得多,但比它的最好情况要差;
2.当初始队列已经排好序或者接近排好,直接插入排序比折半插入排序的比较次数要少。
#include
#include
using namespace std;
vector<int> BinsertSort(vector<int>list) {
int i, j, temp, mid;
int low, high;
for (i = 1; i < list.size(); i++)
{
temp = list[i];
low = 0;
high = i - 1;
while (low <= high) {
mid = (low + high) / 2;
if (temp < list[mid])//temp <= list[mid]会影响稳定性
high = mid - 1;
else
low = mid + 1;
}
/*确定好位置后,将位置之后的数据后移,插入待排序数据*/
for (j = i - 1; j > high; j--)
{
list[j + 1] = list[j];
}
list[high + 1] = temp;
}
return list;
}
int main() {
vector<int>list{ 8,2,1,7,4,9,3,6,5 };
auto res = BinsertSort(list);
for (auto& item : res)
cout << item << " ";
//1 2 3 4 5 6 7 8 9
}
折半插入排序减少了比较元素的次数,约为O(nlogn),比较的次数取决于表的元素个数n。但是移动次数不变,因此,折半插入排序的时间复杂度仍然为O(n²),但它的效果还是比直接插入排序要好。
最坏情况(完全逆序): O ( n 2 ) O(n^2) O(n2)
最好情况(已经排好): O ( n l o g n ) O(nlogn) O(nlogn)
平均情况: O ( n 2 ) O(n^2) O(n2),在 n较大时会减少元素比较的次数,但元素移动次数不变,故仍为 O ( n 2 ) O(n^2) O(n2)
排序只需要一个位置来暂存元素 O ( 1 ) O(1) O(1)
出发点:增大步幅来提升直接插入排序的效率。
先将整个待排序列分割成若干子序列,分别进行直接插入排序,待整个序列中的记录”基本有序“时,再对全体记录进行一次直接插入。
特点:
1)缩小增量,增量序列必须是递减的,最后一个必须是1;
2)增量序列应该是互质的(序列内元素之间最大公约数为1);
3)最后一次只需要少量移动
#include
#include
using namespace std;
vector<int> shellsort(vector<int> a)
{
int n = a.size();
int i, gap, temp, j;
for (gap = n / 2; gap > 0; gap /= 2)//gap最后一次进入循环保证为1
for (i = gap; i < n; i++)//从数组第gap个元素开始
if (a[i] < a[i - gap])//每个元素与自己组内的数据进行直接插入排序
{
temp = a[i];//存起来
j = i - gap;//j不再是i-1,而是i-gap
for (; j >= 0 && a[j] > temp; j -= gap) {
a[j + gap] = a[j];
}
a[j + gap] = temp;//不是j+1而是j+gap
}
return a;
}
int main() {
vector<int>list{ 8,2,1,7,4,9,3,6,5 };
auto res = shellsort(list);
for (auto& item : res)
cout << item << " ";
//1 2 3 4 5 6 7 8 9
}
和增量序列有关
最好情况: O ( n ) O(n) O(n)
最坏情况: O ( n 2 ) O(n^2) O(n2)
平均情况:大约 O ( n 1.3 ) O(n^{1.3}) O(n1.3)
O ( 1 ) O(1) O(1)