排序(1)插入排序

直接插入排序

最简单的排序,做法就是两个遍历,外部的遍历到哪里,哪里就排好了序,然后
1.外遍历序号加1判断该位置上的数是否大于等于(不算等于变成非稳定排序)前面有序序列的最大值,若是,则它现在所在的位置是正确的,重复1。若不是,进行2,对前面的数组做个遍历,将该数作为关键值。
2序号减1,看前一个位置的数是否小于等于关键值,是,则将关键值赋给后一个位置,进行1,否,则将前一个位置赋给后一个位置(注意对序号0的处理,避免越界),重复2。
直接插入排序有带哨兵和不带哨兵两种,区别只是将数组的0号位作为关键值暂存地址还是单独声明一个int暂存。

public class StraightInsertionSort {

	public static void main(String[] args) {
		int[] source1=new int[] {1,3,11,2,7,9,8};
		int[] source2=new int[] {0,1,3,11,2,7,9,8};
		Sis(source1);
		Sis2(source2);
		for(int i=0;i<source1.length;i++) {
			System.out.print(source1[i]+" ");
		}
		System.out.println();
		for(int i=0;i<source2.length;i++) {
			System.out.print(source2[i]+" ");
		}
		System.out.println();
	}
	//无哨兵
	public static void Sis(int[] nums){
		for(int i=1;i<nums.length;i++) {
			//在循环之前要单独把有序表的最后一位后移,为了避免与自身比较产生问题。
			if(nums[i]<nums[i-1]) {
				int thisNeed=nums[i];
				nums[i]=nums[i-1];
				for(int j=i-2;j>=0;j--) {
					if(nums[j]<=thisNeed) {
						nums[j+1]=thisNeed;
						break;
					}
					else {
						nums[j+1]=nums[j];
					}
				}
			}
		}
	}
	//有哨兵
	public static void Sis2(int[] nums){
		for(int i=2;i<nums.length;i++) {
			if(nums[i]<nums[i-1]) {
				nums[0]=nums[i];
				nums[i]=nums[i-1];
				for(int j=i-1;j>0;j--) {
					if(nums[0]<nums[j-1]) {
						nums[j]=nums[j-1];
					}
					else {
						nums[j]=nums[0];
						break;
					}
				}
			}
		}
	}
}

出错记录:把 int thisNeed=nums[i];和nums[i]=nums[i-1];写反了。

折半插入

在插入排序提高效率的探索过程中出现的一种排序,提高的并不明显,其实就是在查找插入位置的时候使用二分查找查找位置,但查找成功后还是要把前面的有序序列中比待插入数大的数一个一个后移为待插入数腾出位置,所以尽管有所优化,但时间复杂度依然是n^2级。

2-路插入

实际上是利用了循环数组的一种插入方法,普通的插入排序是利用现成的空间,第一次在前1位排出一个有序数组,第2次在前2位…,直到把整个数组排序,每次排序只有最后一位是无序的,如果刚好大于所有前面的关键字,则此时前i位数组是有序的,否则据其大小往中间插。

而2-路插入则是申请一个和原数组大小相同的空间(实际上是与待排记录所占空间大小相同的空间),同样的,每次加一个数构成一个临时有序数组,不同的是,有一个first和final标记最大值和最小值,然后在某一个位置插入第1个数,接着每次插入的数字与之前的有序序列作比较,若小于则插在前面,若大于则插在后面,只有小于最大值大于最小值时才会一次和序列中的值作比较知道找到合适的位置。

public class TwoStraightInsertion {
	public static void main(String[] args) {
		int [] source=new int[]{49,38,65,97,76,13,27,49};
		twoStraightInsertion(source);
		for(int i=0;i<source.length;i++) {
			System.out.println(source[i]);
		}
	}
	public static void twoStraightInsertion(int[] RawBuff) {
		//定义首尾指针
		int first=0;
		int last=0;
		int length=RawBuff.length;
		int [] tempBuff=new int[RawBuff.length];
		tempBuff[0]=RawBuff[0];
		for(int i=1;i<RawBuff.length;i++) {
			int value=RawBuff[i];
			//若小于最小值,则插到最小值前面
			if(value<=tempBuff[first]) {
				first=(first-1+length)%length;
				tempBuff[first]=value;
			}
			//若大于最大值,则插到最大值后面
			if(value>=tempBuff[last]) {
				last=(last+1)%length;
				tempBuff[last]=value;
			}
			/*
			 * 错误:1.大于小于写反了2.忘了最后修改last
			 */
			//若大于最小值,小于最大值,则通过移动插到合适的中间位置。
			if((value>tempBuff[first])&&(value<tempBuff[last])) {
				int j=(last+1)%length;
				//tempBuff[j]=tempBuff[last];
				while(value<tempBuff[(j-1+length)%length]) {
					tempBuff[j]=tempBuff[(j-1+length)%length];
					j=(j-1+length)%length;
				}
				tempBuff[j]=value;
				last=(last+1)%length;
			}
		}
		for(int i=0;i<length;i++) {
			RawBuff[i]=tempBuff[first];
			first=(first+1)%length;
		}
	}
}
表插入排序

表排序实际上就是静态链表的插入排序,与直接插入几乎一模一样,只是将原来移动的步骤改为直接修改指针。
但是这样有个问题是排序后的链表在数组中依然是乱的,我们可以从最小值开始一个一个查找刚好比前一个大的值,却无法指定查找第几大的值也无法折半查找,所以还需要对静态链表进行重排。
在不考虑申请额外空间的前提下,重排的过程有点复杂,基本思路是依次将链表的顺序的第i位放到数组的第i位,在原来数组第i位的记录被移走会打乱链表后续的顺序,解决办法是将移动后数组第i位的指针指向移动前该位置上的记录,然后每次交换前判断要交换的是否是已经交换过的,否则则向链表下遍历。

public class LinkedNode {
	public int value;
	public int nextNode;
}
public class TableSort {
	public static void main(String[]args) {
		LinkedNode l1=new LinkedNode();
		LinkedNode l2=new LinkedNode();
		LinkedNode l3=new LinkedNode();
		LinkedNode l4=new LinkedNode();
		LinkedNode l5=new LinkedNode();
		LinkedNode l6=new LinkedNode();
		LinkedNode l7=new LinkedNode();
		LinkedNode l8=new LinkedNode();
		LinkedNode l0=new LinkedNode();
		l0.value=0;
		l1.value=49;
		l2.value=38;
		l3.value=65;
		l4.value=97;
		l5.value=76;
		l6.value=13;
		l7.value=27;
		l8.value=52;
		LinkedNode[] sourceList=new LinkedNode[] {l0,l1,l2,l3,l4,l5,l6,l7,l8};
		tableSort(sourceList);
	}
	public static void tableSort(LinkedNode[] sourceList) {
		//插入排序时不移动位置而是修改指针。
		//需要记录现有的最大值
		//0的位置作为头结点(空);
		sourceList[0].nextNode=1;
		int max=0;
		for(int i=1;i<sourceList.length;i++) {
			int compareNode=0;
			//从头结点向后依次比较
			while(true) {
				//如果遍历到前面有序链表的最大值了,说明待插入值比之前的都大,插在最后。
				if(max==compareNode) {
					sourceList[max].nextNode=i;
					max=i;
					break;
				}
				//与遍历结点的下一个结点的值作比较,小于则插到这之后
				if(sourceList[sourceList[compareNode].nextNode].value>sourceList[i].value) {
					sourceList[i].nextNode=sourceList[compareNode].nextNode;
					sourceList[compareNode].nextNode=i;
					break;
				}
				compareNode=sourceList[compareNode].nextNode;
			}
		}
		//测试静态链表。
		System.out.println("求得的有序链表为:");
		for(int i=0;i<sourceList.length;i++) {
			System.out.println(i+" "+sourceList[i].value+" "+sourceList[i].nextNode+" ");
		}
		//链表重排
		int nextNode=sourceList[0].nextNode;
		LinkedNode temp=new LinkedNode();
		for(int i=1;i<sourceList.length;i++) {
			//先找到第n位
			//通过
			while(nextNode<i) {
				System.out.println(nextNode+" "+i);
				if(nextNode==sourceList[nextNode].nextNode) {
					throw new IllegalArgumentException("死亡循环");
				}
				nextNode=sourceList[nextNode].nextNode;
			}
			int tempNode=sourceList[nextNode].nextNode;
			if(nextNode!=i) {
				temp=sourceList[i];
				sourceList[i]=sourceList[nextNode];
				sourceList[nextNode]=temp;
				sourceList[i].nextNode=nextNode;
			}
			nextNode=tempNode;
		}
		System.out.println("重排记录的结果为:");
		for(int i=0;i<sourceList.length;i++) {
			System.out.println(i+" "+sourceList[i].value);
		}
	}
}

希尔排序

插入排序的时间主要是花在一个一个移动记录找插入位置上,那么最佳的优化方案就是减少移动次数,考虑到极端情况,若待排序列为逆序,所有移动不可避免,时间复杂度为o(n^2),但若为正序,完全不用移动,只需要判断有序即可,时间复杂度便是o(n)了,希尔排序便是从这些角度出发,通过在直接插入排序之前进行若干次子序列排序来使得序列基本有序从而减少插入次数来提高排序的时间效率的。
简单来说,假设你的增量序列为5,3,1,那么第一次就一次对待排序列中的第1,6,11…;
2,7,12…;
3,8,13…;

个数字组成的子序列进行排序,而第二次则对
1,4,7;
2,5,8;
3,6,9
进行排序,而最后一次再进行增量为1的(实际上就是)的排序。(事实)
需要说明的是,
1.因为前面排过的子序列已经有序了,所以在选择增量时,两者之间不能有公因子。
2.因为最后要保证整个序列有序,所以应当全部排一次,增量序列的最后一个增量必然为1(事实上直接插入排序就是序列只有1的特殊的希尔排序)。

public class ShellSort {
	public static void shellInsert(int []sourceList,int key) {
		for(int i=1+key;i<sourceList.length;i++) {
			//如果查找值小于子表中的前一数据,说明该值需要往前插入
			if(sourceList[i]<sourceList[i-key]) {
				//temp值与待插入值进行比较的数的序号
				int temp=i-key;
				//i-key上的值移动到了i的位置上,所以需要对sourceList[i]进行暂存。
				sourceList[0]=sourceList[i];
				sourceList[i]=sourceList[temp];
				//在子序列中不断比较,不断后移,注意此处一定要先判断temp是否大于0,否则会报错
				while(temp>0&&sourceList[temp]>sourceList[0]) {
					sourceList[temp+key]=sourceList[temp];
					temp-=key;
				}
				//此时所有temp上的值已经比待插入值小或等于,插到其后面即可。
				sourceList[temp+key]=sourceList[0];
			}
		}
	}
	public static void shellSort(int[]sourceList,int []increSequence) {
		for(int i=0;i<increSequence.length;i++) {
			shellInsert(sourceList,increSequence[i]);
		}
	}
	public static void main(String[]args) {
		int [] sourceList=new int[] {
				0,
				49,
				38,
				65,
				97,
				76,
				13,
				27,
				49
		};
		int[] increSequence=new int[] {5,3,1};
		shellSort(sourceList,increSequence);
		for(int i=1;i<sourceList.length;i++) {
			System.out.print(sourceList[i]+" ");
		}
		System.out.println();
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值