排序
排序法 | 最差时间分析 | 平均时间复杂度 | 稳定度 | 空间复杂度 |
冒泡排序 | O(n2) | O(n2) | 稳定 | O(1) |
快速排序 | O(n2) | O(n*log2n) | 不稳定 | O(nlog2n) |
选择排序 | O(n2) | O(n2) | 不稳定 | O(1) |
二叉树排序 | O(n2) | O(n*log2n) | 不一顶 | O(n) |
插入排序 | O(n2) | O(n2) | 稳定 | O(1) |
堆排序 | O(n*log2n) | O(n*log2n) | 不稳定 | O(1) |
希尔排序 | O | O | 不稳定 | O(1) |
1.冒泡排序:每次比较两个相邻的元素,将较大的元素交换至右端。
每次冒泡排序操作都会将相邻的两个元素进行比较,看是否满足大小关系要求,如果不满足,就交换这两个相邻元素的次序,一次冒泡至少让一个元素移动到它应该排列的位置,重复N次,就完成了冒泡排序。
优化:即如果当次冒泡操作没有数据交换时,那么就已经达到了有序状态(设置一个flag)
package sort;
import java.util.Arrays;
/**
* @description: 冒泡排序 冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。
* 如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复n 次, 就完成了 n 个数据的排序工作。
**/
public class BubbleSort {
public void bubbleSort(Integer[] arr, int n) {
if (n <= 1)
return; // 如果只有一个元素就不用排序了
for (int i = 0; i < n; ++i) {
// 提前退出冒泡循环的标志位,即一次比较中没有交换任何元素,这个数组就已经是有序的了
boolean flag = false;
for (int j = 0; j < n - i - 1; ++j) { // 此处你可能会疑问的j<n-i-1,因为冒泡是把每轮循环中较大的数飘到后面,
// 数组下标又是从0开始的,i下标后面已经排序的个数就得多减1,总结就是i增多少,j的循环位置减多少
if (arr[j] > arr[j + 1]) { // 即这两个相邻的数是逆序的,交换
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
flag = true;
}
}
if (!flag)
break;// 没有数据交换,数组已经有序,退出排序
}
}
public static void main(String[] args) {
Integer arr[] = { 2, 4, 7, 6, 8, 5, 9 };
//输出数组
System.out.println(Arrays.toString(arr));
BubbleSort bubbleSort = new BubbleSort();
bubbleSort.bubbleSort(arr, arr.length);
//输出排序后的数组
System.out.println(Arrays.toString(arr));
}
}
2.选择排序:每一趟从待排序的数据元素中选择最小(或最大)的一个元素作为首元素,直到所有元素排完为止
在算法实现时,每一轮确定最小元素的时候会通过不断地比较交换来使得首位置为当前最小,交换是个比较耗时的操作。其实我们很容易发现,在还未完全确定当前最小元素之前,这些交换都是无意义的。
因此可以通过设置一个变量min,每一次比较出存储较小元素,并且记录当前元素的数组下标,当本轮循环结束之后,那这个变量min存储的就是当前最小元素的下标,此时再执行交换操作,以此确定本轮遍历的最小元素放到了数组前部。
package sort;
public class SelectSort {
public static void main(String[] args) {
// 模拟数据
int[] array = { 52, 63, 14, 59, 68, 35, 8, 67, 45, 99 };
System.out.println("原数组:");
for (int i : array) {
System.out.print(i + " ");
}
System.out.println();
selectSort(array);
System.out.println("排序后:");
for (int i : array) {
System.out.print(i + " ");
}
}
public static void selectSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
int min = i;
for (int j = i + 1; j < arr.length - 1; j++) {
if (arr[j] < arr[min]) {
min = j;
}
}
if (min != i) {
swap(arr, i, min);
}
}
}
// 完成数组两元素间交换
public static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
3.插入排序:每次将一个待排序的数据元素,插入到前面已经排好序的数列中的适当位置,使数列依然有序;直到待排序数据元素全部插入完为止。
public void InsertSort(int []array) {
int i,j,temp;
for(i=1;i<array.length;i++) {
//默认第一个数有序,从第二个数往前比较
temp=array[i];
for(j=i-1;j>=0;j--) {
//比前一个数大则已排序完毕
if(temp>array[j]) {
break;
}else {
array[j+1]=array[j];
}
}
array[j+1]=temp;
}
}
4.堆排序:堆排序是将数据看成是完全二叉树、根据完全二叉树的特性来进行排序的一种算法
堆是完全二叉树的结构,因此对于一个有n个节点的堆,高度为O(logn)。
最大堆:堆中的最大元素存放在根节点的位置。
最小堆:堆中的最小元素存放在根节点的位置。
参考https://www.cnblogs.com/beaglebone/p/5876745.html
/**
* 堆排序的主要入口方法,共两步。
*/
public void sort() {
/*
* 第一步:将数组堆化
* beginIndex = 第一个非叶子节点。
* 从第一个非叶子节点开始即可。无需从最后一个叶子节点开始。
* 叶子节点可以看作已符合堆要求的节点,根节点就是它自己且自己以下值为最大。
*/
int len = array.length - 1;
int beginIndex = (len - 1) >> 1;
for (int i = beginIndex; i >= 0; i--)
maxHeapify(i, len);
/*
* 第二步:对堆化数据排序
* 每次都是移出最顶层的根节点A[0],与最尾部节点位置调换,同时遍历长度 - 1。
* 然后从新整理被换到根节点的末尾元素,使其符合堆的特性。
* 直至未排序的堆长度为 0。
*/
for (int i = len; i > 0; i--) {
swap(0, i);
maxHeapify(0, i - 1);
}
}
private void swap(int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
/**
* 调整索引为 index 处的数据,使其符合堆的特性。
*
* @param index 需要堆化处理的数据的索引
* @param len 未排序的堆(数组)的长度
*/
private void maxHeapify(int index, int len) {
int li = (index << 1) + 1; // 左子节点索引
int ri = li + 1; // 右子节点索引
int cMax = li; // 子节点值最大索引,默认左子节点。
if (li > len) return; // 左子节点索引超出计算范围,直接返回。
if (ri <= len && array[ri] > array[li]) // 先判断左右子节点,哪个较大。
cMax = ri;
if (array[cMax] > array[index]) { //若“<”这是从大到小
swap(cMax, index); // 如果父节点被子节点调换,
maxHeapify(cMax, len); // 则需要继续判断换下后的父节点是否符合堆的特性。
}
}
5.归并排序:归并排序利用的是分治的思想实现的,对于给定的一组数据,利用递归与分治技术将数据序列划分成为越来越小的子序列,之后对子序列排序,最后再用递归方法将排好序的子序列合并成为有序序列。合并两个子序列时,需要申请两个子序列加起来长度的内存,临时存储新的生成序列,再将新生成的序列赋值到原数组相应的位置。
public class MergeSort {
public static void merSort(int[] arr,int left,int right){
if(left<right){
int mid = (left+right)/2;
merSort(arr,left,mid);//左边归并排序,使得左子序列有序
merSort(arr,mid+1,right);//右边归并排序,使得右子序列有序
merge(arr,left,mid,right);//合并两个子序列
}
}
private static void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[right - left + 1];//ps:也可以从开始就申请一个与原数组大小相同的数组,因为重复new数组会频繁申请内存
int i = left;
int j = mid+1;
int k = 0;
while(i<=mid&&j<=right){
if (arr[i] < arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while(i<=mid){//将左边剩余元素填充进temp中
temp[k++] = arr[i++];
}
while(j<=right){//将右序列剩余元素填充进temp中
temp[k++] = arr[j++];
}
//将temp中的元素全部拷贝到原数组中
for (int k2 = 0; k2 < temp.length; k2++) {
arr[k2 + left] = temp[k2];
}
}
public static void main(String args[]){
int[] test = {9,2,6,3,5,7,10,11,12};
merSort(test,0,test.length-1);
for(int i=0; i<test.length;i++){
System.out.print(test[i] + " ");
}
}
}
关于归并递归的流程可参考https://blog.csdn.net/Cinderella_hou/article/details/51763681
6.快速排序:设置最左边的为一个基准数,先从右边开始找到一个比基准数小的数,再从左边找到一个比基准数大的数,交换这两个数,重复操作直到i==j,把i==j此刻位置的数和基准数交换,基准数归位。基准数将数列分为两个子数列,递归以上操作。
public class QuickSort {
public static void quickSort(int[] arr,int low,int high){
int i,j,temp,t;
if(low>high){
return;
}
i=low;
j=high;
//temp就是基准位
temp = arr[low];
while (i<j) {
//先看右边,依次往左递减
while (temp<=arr[j]&&i<j) {
j--;
}
//再看左边,依次往右递增
while (temp>=arr[i]&&i<j) {
i++;
}
//如果满足条件则交换
if (i<j) {
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
//最后将基准为与i和j相等位置的数字交换
arr[low] = arr[i];
arr[i] = temp;
//递归调用左半数组
quickSort(arr, low, j-1);
//递归调用右半数组
quickSort(arr, j+1, high);
}
public static void main(String[] args){
int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19};
quickSort(arr, 0, arr.length-1);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
以上来自《啊哈算法》