内部排序算法总结

/**
 * 对排序算法的整理
 * 1、插入排序:
 *  (1)直接插入排序
 *  (2)折半插入排序
 *  (3)希尔排序
 * 2、交换排序:
 *  (1)冒泡排序
 *  (2)快速排序
 * 3、选择排序:
 *  (1)简单选择排序
 *  (2)堆排序
 * 4、归并排序和基数排序
 */

本文中的算法实现都对以下序列排序:

int[] a = {3,1,5,7,2,4,9,6};

一、插入排序

基本思想:每次将一个待排序的记录按其关键字大小插入到前面已经排好序的子序列中,直到全部记录插入完成。

1、直接插入排序

假设在排序过程中,待排序表L[1…n],排序过程某一时刻状态:

有序序列L[1…(i-1)]L(i)无序序列L[(i+)…n]

所以为实现将元素L(i)插入到已有序的子序列L[1…(i-1)]中,需要执行以下操作:

(1)查找出L(i)在L[1…(i-1)]中插入的位置K;

(2)将L[k…(i-1)]个元素全部后称移一个位置;

(3)将L(i)复制到L(k)。

void InsertSort(int[] a, int n){
	for(int i=1; i<n; i++){
		if(a[i] < a[i-1]){
			int temp = a[i];
			int j = i-1;
			while(j>=0 && temp<a[j]){
				a[j+1] = a[j];
				j--;
			}
			a[j+1] = temp;
		}
		for(int t = 0; t < n; t++)
			System.out.print(a[t] + "\t");
		System.out.println();
	}
}

空间效率:仅使用了常数个辅助单元,空间复杂度为O(1)

时间效率:最好的情况下数组中元素已经有序O(n),最坏情况数组元素逆序O(n2)

稳定的排序算法,适用于顺序存储和链式存储的线性表。

2、折半插入排序

相比于直接插入排序每趟插入过程都是一边比较一边移动元素,折半插入排序则将比较和移动操作分开,即每次查找到待插入的位置再统一移动待插入位置后面的所有元素

void HalfInsertSort(int[] a, int n){
	for(int i=1; i<n; i++){
		if(a[i] < a[i-1]){
			int temp = a[i];
			int low = 0,high = i-1;
			while(low <= high){
				int mid = (low + high)/2;
				if(a[mid] > temp) 
					high = mid - 1;
				else low = mid + 1;
			}
			for(int j=i-1; j>=high+1; j--)
				a[j+1] = a[j];
			a[high+1] = temp;
		}
		for(int t = 0; t < n; t++)
			System.out.print(a[t] + "\t");
		System.out.println();
	}
}

上述算法仅仅减少了元素的比较次数,约为O(nlog2n),与待排序表初始状态无头,仅取决于表中元素的个数,元素的移动次数没有改变,排序时间复杂度仍为O(n2)。稳定的排序算法。

3、希尔排序

先将待排序的记录序列分割成若干子序列,分别进行直接插入排序,当整个表中元素已呈基本有序时再对全体记录进行一次直接插入排序:

(1)选择一个增量序列t1,t2,...tk,其中ti>tj(i>j),tk=1;

(2)按增量序列个数K对序列进行K趟排序;

(3)每趟排序根据增量ti将待排序列分割成若干长度为m的子序列,分别对各子序列进行插入排序。

 

void ShellSort(int[] a, int n){
	for(int dk=n/2; dk>=1; dk=dk/2){
		for(int i=dk; i<n; i++){
			if(a[i] < a[i-dk]){
				int temp = a[i];
				int j=i-dk;
				for(; j>=0&&temp<a[j]; j-=dk)
					a[j+dk] = a[j];
				a[j+dk] = temp;
			}
		}
		for(int t = 0; t < n; t++)
			System.out.print(a[t] + "\t");
		System.out.println();
	}
}

 

空间效率:仅使用了常数个辅助单元,空间复杂度为O(1)

时间效率:约为O(n1.3),最坏情况下为O(n2)

不稳定的排序算法,仅适用于顺序存储线性表。

二、交换排序

基本思想:根据序列中两个关键字的比较结果来对换这两个记录在序列中的位置。

1、冒泡排序

假设序列长度为n,从后向前(或者从前向后)比较相邻两元素关键字,若为逆序,则交换它们

 

void BubbleSort(int[] a, int n){
	for(int i=0; i<n-1; i++){
		boolean flag = false; //设置标志
		for(int j=n-1; j>i; j--){
			if(a[j] < a[j-1]){
				int temp = a[j];
				a[j] = a[j-1];
				a[j-1] = temp;
				flag = true;
			}
		}
		if(flag == false) //如果一趟下来没有发生交换则已有序 
			return;
		for(int t = 0; t < n; t++)
			System.out.print(a[t] + "\t");
		System.out.println();
	}
}

空间效率:仅使用了常数个辅助单元,空间复杂度为O(1)

时间效率:最好情况下为O(n),最坏情况为O(n2)

稳定排序算法

2、快速排序

在待排序列中L[1…n]中任取一个元素pos作为基准,通过一趟比较,将比pos大的元素全部放在L[(k+1)…n]中、比pos小的元素全部放在L[1…(k-1)]中,则元素pos就放到了最终位置上

 

void QuickSort(int[]a, int low, int high){
	if(low < high){
		int pos = Partition2(a, low, high);			
		QuickSort(a, low, pos-1);
		QuickSort(a, pos+1, high);
	}
}
//partition实现方法一:
int Partition(int[]a, int low, int high){
	int temp =  a[low];
	while(low < high){
		while((low < high) && (a[high] >= temp))
			high--;
		a[low] = a[high];
		while((low < high) && (a[low] <= temp))
			low++;
		a[high] = a[low];			
	}		
	a[low] = temp;
	return low;
}
//partition实现方法二:
int Partition2(int[] num, int low, int high){
	int index = low;
	int tmp = num[high];
	for(int j=low; j<high; j++){
		if(num[j] < tmp){
			int t= num[j];
			num[j] = num[index];
			num[index] = t;
			index++;
		}
	}
	num[high] = num[index];
	num[index] = tmp;
	return index;
}

// golang版本
func quickSort(arr []int, low, high int) {
	if low < high {
		pos := partition(arr, low, high)
		quickSort(arr, low, pos-1)
		quickSort(arr, pos+1, high)
	}
}
func partition(arr []int, low, high int) int {
	val := arr[low]
	for low < high {
		for low < high && arr[high] >= val {
			high--
		}
		arr[low] = arr[high]
		for low < high && arr[low] <= val {
			low++
		}
		arr[high] = arr[low]
	}
	arr[low] = val
	return low
}

空间效率:递归调用需要借用一个栈,其容量与递归最大深度一致,最好情况[log2(n+1)]*,最坏情况O(n),平均情况下为log2n

时间效率:最坏情况下为O(n2)

不稳定的排序算法

三、选择排序

基本思想:每一趟在待排序列中选择关键字最小(或最大)的元素,作为有序子序列的第i个元素,赶到n-1趟做完

1、简单选择排序

每趟选择一个最小元素放到最终位置上

void SelectSort(int[] a, int n){
	for(int i=0; i<n; i++){
		int min = i;
		for(int j=i+1; j<n; j++)
			if(a[j] < a[min])
				min = j;
		if(min != i){
			int temp = a[i];
			a[i] = a[min];
			a[min] = temp;
		}
		for(int t = 0; t < n; t++)
			System.out.print(a[t] + "\t");
		System.out.println();
	}
}

空间效率:仅使用常数个辅助单元,故空间效率为O(1)

时间效率:元素之间的比较始终是n(n-1)/2,所以时间复杂度为O(n2)

不稳定的排序算法

2、堆排序

树形选择排序方法,排序过程将序列看成是一棵完全二叉树的顺序存储结构,利用完全二叉树双亲结点和孩子结点之间的关系在当前无序区中选择关键字最大(或最小)的元素

堆的定义:小根堆:L[i]<=L[2i]且L[i]<=L[2i+1],或 大根堆:L[i]>=L[2i]且L[i]>=L[2i+1]

以构造小根堆为例,N个结点的完全二叉树最后一个结点是第[N/2]个结点的孩子。

(1)对第[N/2]个结点为根的子树进行筛选,若根结点的关键字大于左右子女中关键字较小者,则交换,使该子树成为堆;

(2)之后向前依次对各结点([N/2]-1 ~ 1)为根有子树进行筛选,看该结点值是否大于左右子结点的值,若不是则将左右子树结点中较大值与之交换,此时可能破坏下一级的堆,于是继续采用上述方法构造下一级的堆,直到以该结点为根的子树构成堆为止;

(3)重复上述调整堆的方法至根结点。(调整过程如下图所示)

堆排序的思路则为:首先将存放在L[1…n]中的n个元素建成初始堆,由于堆本身的特点(以大顶堆为例),堆顶元素就是最大值,输出堆顶元素后通常将堆底元素送入堆顶,此时根结点已不满足大顶堆的性质,堆被破坏,将堆顶元素向下调整使其继续保持大顶堆的性质,再输出堆顶元素。如此重复,直到堆中剩下仅一个元素为止:

//堆排序
void BuildHeap(int[] a, int n){//创建堆
	for(int i=(n-1)/2; i>=0; i--){
		AdjustDown(a, i, n-1);
		
//			for(int t = 0; t < n; t++)
//				System.out.print(a[t] + "\t");
//			System.out.println();
	}
}
void AdjustDown(int[] a, int k, int n){//调整堆
	int temp = a[k];
	for(int i=2*k+1; i<=n; i*=2){//数组下标从0开始,则双亲与子女下标关系:Parent*2+1=LeftChild
		if((i<n)&&(a[i]<a[i+1]))//比较左右子树,取较大者
			i++;
		if(temp>=a[i]) break;//本身是大根堆就什么都不做
		else{//否则就选取左右子孩子中较大值交换
			a[k] = a[i];
			k = i;
		}
	}
	a[k] = temp;
}
void HeapSort(int[] a, int n){//堆排序
	BuildHeap(a, n);		
	
	for(int t = 0; t < n; t++)//建立的大根堆
			System.out.print(a[t] + "\t");//输出为大根堆
		System.out.println();
	
	for(int i=n-1; i>0; i--){
		int temp = a[i];
		a[i] = a[0];
		a[0] = temp;          //输出堆顶元素
		AdjustDown(a, 0, i-1);//整理,把剩余的i-1个元素整理成堆	
		
		for(int t = 0; t < n; t++)
			System.out.print(a[t] + "\t");
		System.out.println();
	}		
}

建堆输出:


堆排序输出(每次输出堆顶元素即将堆顶元素置于堆底,再重新对剩余的元素建堆):

空间效率:仅使用了常数个辅助单元,空间复杂度为O(1)

时间效率:建堆时间为O(n),之后有n-1次向下调整的操作,每次调整的时间复杂度为O(h),所以平均时间复杂度为n(log2n)

不稳定的排序算法

四、二路归并排序

基本思想:归并排序是将两个或两个以上的有序表组合成一个新的有序表。假定待排记录有n个记录,可以看成是n个有序的子表,每个子表长度为1,然后两两归并,得到[n/2]*个长度为2(也可能有一个长度为1的)有序表,再两两归并,赶到合并成一个长度为n的有序表为止,如图所示:

 

void Merge(int[] a, int low, int mid, int high){
	int[] b = new int[a.length];
	for(int k=low; k<=high; k++)
		b[k] = a[k];
	int i=low, j=mid+1, k=i;
	for(; i<=mid&&j<=high; k++){
		if(b[i] <= b[j])
			a[k] = b[i++];
		else a[k] =  b[j++];
	}
	while(i <= mid) a[k++] = b[i++];
	while(j <= high) a[k++] = b[j++];
}
void MergeSort(int[] a, int low, int high){
	if(low < high){
		int mid = (low+high)/2;
		MergeSort(a, low, mid);
		MergeSort(a, mid+1, high);
		Merge(a, low, mid, high);
	}
	for(int t = low; t < high; t++)
		System.out.print(a[t] + "\t");
	System.out.println();
}

 

空间效率:辅助单元占用O(n),故空间复杂度为O(n)

时间效率:每一趟的时间复杂度为O(n),共需要[log2n]趟归并,故时间复杂度为O(nlog2n)

稳定的排序算法

五、基数排序

基本思想:采用多关键字排序思想,借助“分配”和“收集”两种操作对关键字进行排序。

最高位优先(MSD)排序:

1)先按k1 排序分组,将序列分成若干子序列,同一组序列的记录中,关键码k1 相等
2)再对各组按k2 排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd 对各子组排序后
3)再将各组连接起来,便得到一个有序序列。

最低位优先(LSD)排序:

1) 先从kd 开始排序,再对kd-1进行排序,依次重复,直到按k1排序分组分成最小的子序列后
2) 最后将各个子序列连接起来,便可得到一个有序的序列

最低位优先程序,待排序数据如下:

int[] data = new int[] { 1100, 192, 221, 12, 23 };

 

void RadixSort(int[] data, int radix, int distance){
	int [] temp = new int[data.length];//用于暂存元素
	int[] count = new int[radix];//用于计数排序  
	int divide = 1;
	
	for(int i=0; i<distance; i++){
		System.arraycopy(data, 0, temp, 0, data.length);
		Arrays.fill(count, 0);
		
		for(int j=0; j<data.length; j++){
			int tempKey = (temp[j]/divide)%radix;
			count[tempKey]++;
		}
		
		for(int j=1; j<radix; j++)
			count[j] = count[j] + count[j-1];
		
		for (int j = data.length - 1; j >= 0; j--) {  
			int tempKey = (temp[j]/divide)%radix;  
			count[tempKey]--;  
			data[count[tempKey]] = temp[j];  
		}  
		  
		divide = divide * radix; 
	}		
	
	for (int i = 0; i < data.length; i++) {  
		System.out.print("  " + data[i]);  
	} 
}

 


空间效率:一直趟排序需要辅助空间为r,以后重复利用这些队列,所以基数排序的空间复杂度为O(r)

时间效率:需要进行d趟分配和收集,一趟分配需要O(n),一趟收集需要O(r),故时间复杂度为O(d(n+r))

稳定的排序算法

期末大总结:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值