插入排序
插入排序将序列分成已排序和未排序两部分。每次从未排序序列中拿一个元素,接着找到这个元素在已排序中可以插入的位置后插入,然后从未排序序列中将这个元素删除,这就完成了一个元素的排序。类似于我们抓扑克牌,手上的牌都拍好了序,桌上的拍没有排好序,每次我们从桌上抓取一张牌插入到手上的牌中。重复这个过程,我们就可以得到一个排好序的序列。
下面我们使用代码来实现插入排序。
/*
思路:
1. 从未排序的序列中拿取元素ele
2. 在已排序的序列中找到元素ele的插入位置
3. 在找到的位置中插入元素ele
4. 重复上述直到未排序的序列中没有元素
*/
@Test
public void insertSort() {
int[] arr = { 1, 23, 41, 231, 31, 5, 4, 2, 67 };
for (int i = 1; i < arr.length; i++) { // 控制插入的数字为第几个
int temp = arr[i]; // 需要插入的元素
int index = i - 1; // 记录temp在已排好序的位置
// 寻找插入位置
while(index >= 0 && temp<arr[index]) { // 找位置
// 将元素往后移动一个位置,为temp腾出插入空间
arr[index+1] = arr[index];
index--;
}
arr[index+1] = temp; // 插入
}
}
上述的for循环中,arr[0 … i] 总是保存已经排好序的序列,而arr[i+1 … arr.length-1]总是保存着未排序的序列,我们把这种性质称为循环不变式,下面我们先介绍循环不变式。
循环不变式
循环不变式主要帮助我们来理解算法的正确性,关于循环不变式总是有三条性质需要我们证明
- 初始化
循环的第一次迭代之前,它为真 - 保持
如果循环的某次迭代之前它为真,那么下一次迭代它也为真 - 终止
在循环终止时,不变式为我们提供一个有用的性质,该性质有助于证明算法是正确的。
在插入排序中,初始化arr[0 … i] 中只有一个元素,并已经排好序,arr[i+1 … arr.length - 1] 有a.length -1 个元素,且无序。循环体内的代码为我们保持初始化的中的特性,当i = arr.length时,循环终止。
插入排序的最坏情况下的时间复杂度为 O ( n 2 ) O(n^2) O(n2),外层循环循环n次,里层循环n次。
希尔排序
希尔排序源自于Donald Shell,希尔排序是通过比较一定间隔的元素来进行排序的,每一趟比较所用的距离随着算法的进行而缩小,直到只比较相邻元素的下一趟排序为止,因为这个原因希尔排序也叫缩减增量排序。希尔排序中影响排序性能的可控变量是**增量序列(如序列h1,h2,h3…ht,其中h1等于1)**的选择,合适的增量序列能够提高性能。Shell建议的增量序列是ht = N/2 、hk = hk+1/2。下面给出进行一次希尔排序
下面是希尔排序的代码实现
@Test
public void shellSort1() {
int[] arr = {12,32,123,42,121,41,56,72,45};
for(int gap = arr.length/2 ; gap > 0 ; gap /=2) {// 计算增量序列,终止条件gap=0
for(int i = gap ; i < arr.length; i++) { // 从index = gap开始逐渐比较
int temp = arr[i]; // 保存gap的值,下面可能会被覆盖
int index; // 保存temp应该放置的为止
// 间隔比较,满足条件则交换
for(index = i ; index >= gap && temp < arr[index-gap] ; index-=gap) {
arr[index] = arr[index-gap];
}
arr[index] = temp; // 将temp放到对于的为止
}
}
}
每次循环后都有 a r