常用的内部排序算法(java实现,非递减写法)

常用的内部排序算法(java实现,非递减写法),第1张

插入排序

直接插入排序,联想记忆——打扑克牌。

  • 平均时间复杂度O(n^2)
  • 稳定排序
  • 更适合于初始记录基本有序的情况
public void insertSort(int[] a) {
	for (int i = 1; i < a.length; i++) {
		//需要插入排序
		if (a[i] < a[i - 1]) {
			int tmp = a[i];  //哨兵sentry
			a[i] = a[i - 1];
			int j = i - 2;
			for (; j >= 0 && tmp < a[j]; j--) {  //从后往前
				a[j + 1] = a[j];
			}
			a[j + 1] = tmp;
		}
	}
}

折半插入排序,根据待插入的序列已经排序好的特点,可以利用二分查找的方法提高插入位置的查找效率(比较次数显著降低)。

  • 平均时间复杂度O(n^2)
  • 稳定排序
  • 适合初始记录无序,n较大时
public void bInsertSort(int[] a) {
	for (int i = 1; i < a.length; i++) {
		int tmp = a[i]; //监视哨
		int left = 0, right = i - 1;
		while (left <= right) {
			int mid = left + (right - left) / 2;
			if (tmp < a[mid]) {
				right = mid - 1;
			} else {
				left = mid + 1;
			}
		}
		//待插入位为right+1, 从right+1开始右移
		for (int j = right + 1; j < i; j++) {
			a[j + 1] = a[j];
		}
		a[right + 1] = tmp;
	}
}
交换排序

冒泡排序,每排序两两比较相邻的记录,如果发生逆序就交换,从而使小的关键字如气泡一般往左移(或者大的关键字如气泡一般往右移)。

  • 平均时间复杂度O(n^2)
  • 最好情况是序列为正序只需进行一趟排序,过程只进行n-1次比较且不移动记录
  • 最坏情况是序列为逆序。需要进行n-1趟排序
  • 稳定排序
  • 移动记录次数较多,平均性能比直接插入排序差,当初始序列无需,n较大时不宜使用
public void bubbleSort(int[] a) {
	int flag = 1; //每一趟的标记位,如果该趟没有发生排序,说明序列已经正序了
	for (int i = 0; i < a.length - 1 && flag == 1; i++) {
		flag = 0;
		for (int j = 0; j < a.length - 1 - i; j++) {
			if (a[j] > a[j + 1]) {
				flag = 1;
				int tmp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = tmp;
			}
		}
	}
}

快速排序,由冒泡排序改进,每趟排序以选定的枢轴为分界,分别将小于枢轴的记录交换到枢轴左边,大于枢轴的记录交换到枢轴右边,并返回枢轴下标,最终结合递归完成算法

  • 平均时间复杂度O(nlogn)
  • 最坏时间复杂度O(n^2) ,递归树退化为线性表
  • 不稳定排序
  • 适合初始记录无序、n较大的场景,当n较大时,平均情况下快排是所有内部排序方法中最快的一种
public int partition(int[] a, int left, int right) {
	//枢轴记录(默认选最左)
	int pivot = a[left]; 
	while (left < right) {
		while (a[right] >= pivot && left < right) right--;
		a[left] = a[right];
		while (a[left] <= pivot && left < right) left++;
		a[right] = a[left];
	}
	a[left] = pivot;
	return left;
}

public void quickSort(int[] a, int left, int right) {
	if (left < right) {
		int pivotIndex = partition(a, left, right);
		quickSort(a, left, pivotIndex - 1);
		quickSort(a, pivotIndex + 1, right);
	}
}

public void quickSort(int[] a) {
	quickSort(a, 0, a.length - 1);
}
选择排序

基本思想:每一次从待排序的记录中选出最小的记录,按顺序放在已排序的记录序列最后面,直到全部排完为止。

简单选择排序(Simple Selection Sort),或称直接选择排序

  • 平均时间复杂度O(n^2),因为无论初始序列排列如何,所需进行记录间的比较次数都是n(n-1)/2,即 n^2
  • 就选择排序算法本身而言,它是一种稳定的排序方法,但是简单选择排序由于所采用的“交换记录”策略的问题,造成了这种简单选择排序是不稳定的
  • 移动记录次数较少(相反比较次数较多且恒定),当每一个记录占用的空间较多时,此方法比直接插入排序快
public void selectSort(int[] a) {
	for (int i = 0; i < a.length; i++) {
		int min = i;
		//find the index of min
		for (int j = i + 1; j < a.length; j++) {
			if (a[j] < a[min]) {
				min = j;
			}
		}
		//exchange
		int tmp = a[i];
		a[i] = a[min];
		a[min] = tmp;
	}
}

堆排序,改进简单选择排序应从减少“比较次数”角度出发,在n个记录中选出最小值,至少需要进行n-1次比较,若能利用前n-1次比较所得的信息,那么接下来从剩余n-1个记录选择最小值就不需要再进行n-2次比较,以后的各趟以此类推。堆排序是一种树形排序,它利用了完全二叉树来实现上述说的比较信息的存储。

  • 平均、最差时间复杂度均为O(nlogn)
  • 不稳定排序
  • 记录数较少时不宜使用,因为建堆比较耗时;记录数较多时较为高效
private void adjustHeap(int[] a, int s, int m) {
	//a[s+1, ..., m]已经是大根堆
	int heap = a[s];
	for (int i = 2 * s + 1; i <= m; i = i * 2 + 1) {
		//找出左右孩子中的最大节点
		if (i < m && a[i] < a[i + 1]) ++i;
		//堆顶最大,无需再调整
		if (heap >= a[i]) break;
		//堆顶往下筛选
		a[s] = a[i];
		s = i;
	}
	a[s] = heap;
}

private void createHeap(int[] a) {
	//完全二叉树节点数为N时,所有序号(从0开始)大于 (N/2下界值 - 1)的节点都是叶子,以这些叶子为根的子树均是堆
	//自底向上调整
	int n = a.length;
	for (int i = n / 2 - 1; i >= 0; i--) {
		adjustHeap(a, i, n - 1);
	}
}

public void heapSort(int[] a) {
	createHeap(a);
	for (int i = a.length - 1; i > 0; i--) {
		//依次将栈顶元素与最后一个元素交换
		int tmp = a[i];
		a[i] = a[0];
		a[0] = tmp;
		adjustHeap(a, 0, i - 1);
	}
}

归并排序

就是将两个或两个以上的有序表合并成一个有序表的过程。

2-路归并,将两个有序表合并成一个有序表的过程,最简单最常用。

  • 平均时间复杂度O(nlogn)
  • 稳定排序
//两个相邻有序子序列的归并
private void merge(int[] a, int left, int mid, int right) {
	int[] b = new int[a.length]; //临时数组
	int i = left, j = mid + 1, k = left;
	while (i <= mid && j <= right) {
		if (a[i] <= a[j]) b[k++] = a[i++];
		else b[k++] = a[j++];
	}
	while (i <= mid) b[k++] = a[i++]; //将剩余的a[i,...mid]加入到b中
	while (j <= right) b[k++] = a[j++]; //同上
	for (i = left; i <= right; i++) {
		a[i] = b[i];
	}
}

private void mergeSort(int[] a, int left, int right) {
	if (left < right) {
		int mid = left + (right - left) / 2;  //序列一分为二
		mergeSort(a, left, mid);  //对左子序列进行归并排序
		mergeSort(a, mid + 1, right);  //对右子序列进行归并排序
		merge(a, left, mid, right);  //左右归并,得出结果
	}
}

public void mergeSort(int[] a) {
	int n = a.length;
	mergeSort(a, 0, n - 1);
}

扩展:排序算法的稳定性指的是什么?

稳定性:稳定排序算法会让原本有相等键值的纪录维持相对次序。也就是如果一个排序算法是稳定的,当有两个相等键值的纪录R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。
假设参加排序的元素有如下:
(4, 1) (3, 1) (3, 7)(5, 6)

不同排序算法产生如下结果:
① (3, 1)  (3, 7)  (4, 1)  (5, 6)  (相同键值的次序保持不变)
② (3, 7)  (3, 1)  (4, 1)  (5, 6)  (相同键值的次序被改变)

我们就说算法①是稳定排序,算法②是不稳定排序。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/922930.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存