插入排序---直接插入排序

思路

  • 首先将给定数组的第一个元素看作有序序列,有序序列后续的一个元素看作带插入数据,带插入数据后面的序列为无序序列.
    图1-1
  • 现在默认是升序排序,接下来的每一步,将待插入数据从有序序列的后面元素开始往前逐一比较,如果遇到比待插入数据大的元素,就将该元素往后移动,腾出来位置,按照这样的思路将无序序列中的每一个数据都插入到有序序列中。

无哨兵位

无哨兵位就是平常的思路,比如给定一个长度为10的数组,将其按照直接插入排序, C + + C++ C++代码如下:

#include<iostream>
using namespace std;

void sort(int array[], int n){
	for(int i=1;i<n;i++){
		int temp = array[i];
		int j = i-1;
		for(;array[j]>temp;j--){  // 无哨兵和有哨兵的区别
		// 无哨兵需要判断j和0的大小,而且要保证0索引也移动
			if(j<0) break; 
			array[j+1] = array[j];
		}
		
		// 判断j如果小于0,说明全部大于temp,大于等于0说明已经找到待插入位置
		if(j>=0) array[j+1] = temp;
		// j<0说明都是
		else  array[0] = temp; 
	}
}

int main(){
	int N = 10;
	int array[N] = {4, 3, 6, 8, 4, 5, 1, 2, 9, 6};
	sort(array, N);
	for(int i=0;i<N;i++){
		cout << array[i];
	}
	cout << endl;
	return 0;
} 

无哨兵位的情况下,经过里面的for循环之后,有两种结果:找到了一个插入的位置;没有找到插入的位置(其实可以说找到了,只是因为所有的元素已经比较完了,都没有找到一个小于待插入元素的值)。跳出循环之后,需要判断 j j j与的大小,如果j大于等于0,则待插入的位置为 j + 1 j+1 j+1;如果 j j j小于0,则待插入位置就是0.

有哨兵位

有哨兵位相对于无哨兵位多了一个条件,即在创建数组存储初始数据的时候,在数组头留出一个位置,这个位置不用于存放有序序列,充当的是无哨兵位方法中的临时变量,防止需要插入的元素被覆盖,它的好处是代码更加简洁,而且减少了 i f if if判断的次数;缺点是多了一个哨兵位,占用额外的内存空间。

#include<iostream>
using namespace std;

void sort(int array[], int N){
	for(int i=2;i<N;i++){
		array[0] = array[i];  // array[0]为哨兵位
		int j;
		for(j = i-1;array[0]<array[j]&&j>0;j--){
			array[j+1] = array[j];
		}
		array[j+1] = array[0];
	}
} 
int main(){
	int N = 11;
	int array[N] = {0, 3, 5, 2, 6, 8, 4, 2, 9, 3, 1};
	sort(array, N);
	for(int i=1;i<N;i++){
		cout << array[i];
	}
	return 0;
}

稳定性

从上面的两种方法中看出,不论是哨兵位还是无哨兵位,只有在待插入元素小于当前元素的时候才移动,等于的时候不会移动(升序),即能保持数组中两个大小相同的元素在排序前的前后关系,所以是稳定的。

效率

  • 时间复杂度
    最好的情况是,原序列是升序的,那么内部循环不用执行,时间复杂度为 O ( n ) O(n) O(n);最坏的情况是,原序列是降序的,内外都全部执行,时间复杂度为 O ( n 2 ) O(n^2) O(n2)

  • 空间复杂度
    需要一个长度为n的数组来存储原式数据,然后额外的临时变量存储待插入数据(对于哨兵位,数组的长度为 n + 1 n+1 n+1),空间复杂度为 O ( n + 1 ) = O ( n ) O(n+1) = O(n) O(n+1)=O(n)

[注]:直接插入排序使用链表也可以实现,在众多排序算法中,可以同时适应两种数据结构的并不多。

附加思路

下面这段代码是我在刚了解完直接插入排序的思路之后,动手实现的,在这段代码中,表面上看是三重循环,但是这个第三重循环所实现的仅仅是移动元素,什么意思?直接插入排序中,第一层循环是遍历无序序列,第二层循环是将待插入元素与当前指向的元素进行比较与元素的移动。我的思路是先循环比较,然后统一进行移动,而不是比较一个移动一个,那这样的话为什么要把这个移动的循环嵌入循环比较里面呢?放在外面和循环比较同一个层次不是更容易理解吗?因为我的代码一开始设置的循环比较中的变量 j j j只能在 f o r for for循环里有效,在外部无效,所以我就顺理成章的方进去了,当然设置成全局变量是可以将这个循环拿到第二层循环的。

#include<iostream>
using namespace std;

void DirectInsertSort(int array[],int n){
	for(int i=1;i<n;i++){  // 从第二个元素开始决定插入 
		for(int j=i-1;j>=0;j--){ // 跟前面的所有元素比较大小 
			if(array[i]>=array[j]){  // 如果大于当前指向的元素,插入当前元素的后一个位置 
				int keep = array[i];
				for(int temp=i-1;temp>j;temp--){  // 将后面的元素全部后移 
					array[temp+1] = array[temp];
				} 
				array[j+1] = keep;  // 插入新元素 
				break;
			}
			if(j==0){  // 如果比较到了最后一个都没有插入 
				int keep = array[i];
				for(int temp=i-1;temp>=0;temp--){
					array[temp+1] = array[temp];
				}
				array[0] = keep;  // 插入到数组的第一个位置 
			}
		}
	}
	
} 
int main(){
	int N = 10;
	int array[N] = {3, 4, 8, 2, 3, 0, 7, 9, 3, 3};
	DirectInsertSort(array, 10);
	for(int i= 0;i<N;i++){
		cout << array[i];
	}
	return 0;
} 

猜你喜欢

转载自blog.csdn.net/weixin_43141320/article/details/113728419