排序的基本要义是把任意序列的数据元素重排成按关键字有序的序列。
排序过程包含两个基本操作:
(1)比较两个关键字的大小。
(2)将记录从一个位置移动到另一个位置。
插入排序是排序方法中的一类,这里介绍三种类型的插入排序。
一、直接插入排序
最简单的排序方式,基本操作为将一个记录插入到已经排好序的有序列表中,得到新的有序列表。(即将序列中的第一个记录看成是一个有序子序列,从第二个记录开始逐条进行插入,直到整个序列都按关键字非递减有序为止。)
时间复杂度最好为O(n),最坏为O(n^2),是一种稳定的排序方式(两个相同的关键字排序完成后,后面的不会被交换到前面)。
具体实现如下:
public class Demo01 {
public static void main(String[] args) {
int[] arr=new int[9];
arr[0]=0;
arr[1]=49;
arr[2]=38;
arr[3]=65;
arr[4]=97;
arr[5]=76;
arr[6]=13;
arr[7]=27;
arr[8]=49;
InsertSort(arr);
for(int i=0;i<arr.length;i++){
System.out.print(arr[i]+" ");
}
}
private static int[] InsertSort(int[] arr) {
for(int i=2;i<arr.length;i++){
if(arr[i]<arr[i-1]){
arr[0]=arr[i];
arr[i]=arr[i-1];
int j;
for(j=i-2;arr[0]<arr[j];j--){
arr[j+1]=arr[j];
}
arr[j+1]=arr[0];
}
}
return arr;
}
}
其中arr[0]处为监视哨,提供数字交换,避免下标越界。
二、希尔排序
希尔排序与直接插入排序类似,不同的地方在于希尔排序是将相隔某个“增量”的记录组成一个子序列,关键字的比较不再是一步一步的挪动,而是跳跃式移动。
这样做的好处有两个:
(1)直接插入排序算法简单,n较小时效率也较高(时间复杂度O(n^2))。
希尔排序将序列分割为子序列,提高了排序效率。
(2)直接插入排序在序列“基本有序”时,时间复杂度接近于O(n)。
希尔排序经过多次重排,使得序列在全体记录进行直接插入排序时已经“基本有序”。
需要注意的是,在选择希尔排序增量序列的时候,应该使增量序列中的值没有除了1之外的公因子,且最后一个增量值必须为1 。时间复杂度O(nlogn) 。
public class Demo01 {<pre class="java" name="code"> //对顺序表arr做一趟希尔插入排序
private static void ShellInsert(int[] arr,int dk){
for(int i=dk+1;i<arr.length;i++){
if(arr[i]<arr[i-dk]){
arr[0]=arr[i];
int j;
for(j=i-dk;j>0&&arr[0]<arr[j];j-=dk)
arr[j+dk]=arr[j];
arr[j+dk]=arr[0];
}
}
}
//按增量序列对顺学表做希尔排序
private static void ShellSort(int[] arr,int[] dlta,int t){
for(int k=0;k<t;k++){
ShellInsert(arr, dlta[k]);
}
}
public static void main(String[] args) {
int[] arr=new int[9];
arr[0]=0;
arr[1]=49;
arr[2]=38;
arr[3]=65;
arr[4]=97;
arr[5]=76;
arr[6]=13;
arr[7]=27;
arr[8]=49;
int t=3;
int[] dlta=new int[t];
dlta[0]=5;
dlta[1]=3;
dlta[2]=1;
ShellSort(arr, dlta, t);
for(int i=0;i<arr.length;i++){
System.out.print(arr[i]+" ");
}
}
}
三、其他插入排序
为了优化直接插入排序,想办法减少“比较”和“移动”的次数。
1.折半插入排序
查找操作采用“折半查找”,减少了关键字的比较次数,关键字移动次数不变。时间复杂度仍然是O(n^2) 。
public class Demo02 {
public static void main(String[] args) {
int[] arr=new int[9];
arr[0]=0;
arr[1]=49;
arr[2]=38;
arr[3]=65;
arr[4]=97;
arr[5]=76;
arr[6]=13;
arr[7]=27;
arr[8]=49;
BInsertSort(arr);
for(int i=0;i<arr.length;i++){
System.out.print(arr[i]+" ");
}
}
private static int[] BInsertSort(int[] arr) {
for(int i=2;i<arr.length;i++){
int low=1,high=i-1;
arr[0]=arr[i];
while(low<=high){
int m=(high+low)/2;
if(arr[m]<arr[0]){
low=m+1;
}else{
high=m-1;
}
}
for(int j=i-1;j>=high+1;j--){//记录后移
arr[j+1]=arr[j];
}
arr[high+1]=arr[0];
}
return arr;
}
}
2.2-路插入排序
消耗n个记录的辅助空间,减少了记录移动的次数。其做法是另设一个同类型的临时数组,排序过程中将有序数据存放在临时数组中。
2-路插入排序使用了非常巧妙的求余函数。
public class Demo03 {
public static void main(String[] args) {
int[] arr=new int[9];
arr[0]=0;
arr[1]=49;
arr[2]=38;
arr[3]=65;
arr[4]=97;
arr[5]=76;
arr[6]=13;
arr[7]=27;
arr[8]=49;
TwoRoadInsertSort(arr);
for(int i=0;i<arr.length;i++){
System.out.print(arr[i]+" ");
}
}
private static int[] TwoRoadInsertSort(int[] arr) {
final int iLength=arr.length-1;
int first=0,final1=0;
int[] iTempBuff=new int[iLength];
iTempBuff[0]=arr[1];
for(int i=2;i<arr.length;i++){
if(arr[i]<iTempBuff[first]){
first=(first+iLength-1)%iLength;
iTempBuff[first]=arr[i];
}
if(arr[i]>iTempBuff[final1]){
final1++;
iTempBuff[final1]=arr[i];
}
if(arr[i]>iTempBuff[first]&&arr[i]<iTempBuff[final1]){
int j=final1++;
while(arr[i]<iTempBuff[j]){
iTempBuff[(j+iLength+1)%iLength]=iTempBuff[j];
j=(j+iLength-1)%iLength;
}
iTempBuff[(j+iLength+1)%iLength]=arr[i];
}
}
for(int k=0;k<iLength;k++){
arr[k+1]=iTempBuff[(first++)%iLength];
}
return arr;
}
}
3.自定义链表插入排序。
(1)表结点类
package demo02;
class Node {
private int data;
private Node next;
public Node(){
next=null;
}
public Node(int data, Node next) {
super();
this.data = data;
this.next = next;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
(2)链表类
package demo02;
class LinkList {
private Node head=null;
private int size=0;
public LinkList(){
head=new Node();
size=0;
}
public boolean addAt(int i,int elem){
if(i<0||i>size){
return false;
}
Node pre,curr;
for(pre=head;i>0&&pre.getNext()!=null;i--,pre=pre.getNext())
;
curr=new Node(elem, pre.getNext());
pre.setNext(curr);
size++;
return true;
}
public Node getHead() {
return head;
}
public void setHead(Node head) {
this.head = head;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public boolean isEmpty(){
return (size==0);
}
public void listAll(){
for(Node curr=head.getNext();curr!=null;curr=curr.getNext()){
System.out.print(curr.getData()+" ");
}
System.out.println();
}
public void bubbleSort(){
Node p,q;
int temp;
for(p=head.getNext();p.getNext()!=null;p=p.getNext()){
for(q=head.getNext();q.getNext()!=null;q=q.getNext()){
if(q.getData()>q.getNext().getData()){
temp=q.getData();
q.setData(q.getNext().getData());
q.getNext().setData(temp);
}
}
}
}
}
(3)主方法类
package demo02;
import java.util.Scanner;
public class TestLinkList {
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
int size;
System.out.println("请输入链表节点数:");
size=scan.nextInt();
LinkList link=new LinkList();
for(int i=0;i<size;i++){
link.addAt(i, scan.nextInt());
}
link.listAll();
link.bubbleSort();
link.listAll();
}
}