所谓排序算法: 冒泡、插入、希尔、选择、快速、归并、桶排序、计数排序、基数排序
时间复杂度为O(n²)
冒泡排序
过程
每一次冒泡都会遍历整个数组,依次对比相邻的元素,符合条件就交换;
原来 : 1 5 6 8 2
第一次排序: 1 5 6 8 2 (8>2)交换 8的最终位置找到了
1 5 6 2 8
第二次排序 1 5 6 2 8
1 5 2 6 8 (6>2) 交换 6的位置找到了;
......
java实现代码
int[] a = {5,3,8,4,2,1};
int n = a.length;
for (int i = 0 ; i<n ; i++){
boolean flag = false; //提前退出冒泡的标志
for (int j = 0 ; j<n-i-1;j++){
if (a[j]>a[j+1]){
int tem = a[j];
a[j] = a[j+1];
a[j+1] = tem;
flag = true;
}
}
if (!flag) break;
}
总结:
- 每一次冒泡遍历一整个数组,要为每个元素找到正确的位置,所以时间复杂度为O(n²);
- 每一次冒泡肯定能找到某个元素正确的位置
- 如果我们设定如果相同的元素对比不交换,那么这个冒泡就是稳定排序算法;
- 因为冒泡排序只涉及到相邻元素的交换,所以该算法为原地排序算法;
插入排序
插入排序就是用新数据一次与数组中的元素进行比较,找到它应该插入的位置,再插入。
过程
将他分成已排序区和未排序区:
例如 5,3,8,4,2,1 设5为已排序区 3,8,4,2,1 为未排序区
将第二个元素3与5比较,如果3大,那么3跟5交换 3,5就成为已排序区; 3,5,8,4,2,1
java代码:
int[] a = {5,3,8,4,2,1};
int n = a.length;
for (int i = 1; i < n; i++) {
int value = a[i]; //value为要插入的数据
int j = i-1; //j是已排序好的区间索引
for (;j>=0;j--){
if (a[j]>value){ //交换
a[j+1] = a[j];
}else {
break;
}
}
a[j+1] = value; //是j+1
}
总结:
- 稳定原地算法
- 最好情况时间复杂度是O(n)因为,如果数组是有序的,不用插入数据,每次也只用比较一个元素就可以确定插入位置,最坏肯定还是O(n²)
3.插入比冒泡更优的地方在于,交换操作,冒泡交换三个语句,插入只需要一个,理论上较快;
希尔排序
他是插入排序的改进版
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。
public static void shellSort(int[] arr) {
int length = arr.length;
int temp;
for (int step = length / 2; step >= 1; step /= 2) {
for (int i = step; i < length; i++) {
temp = arr[i];
int j = i - step;
while (j >= 0 && arr[j] > temp) {
arr[j + step] = arr[j];
j -= step;
}
arr[j + step] = temp;
}
}
}
选择排序
选择排序跟插入排序相似,也是将数组分成未排序区和已排序区,但是选择排序是在未排序区将最小的元素放在已排序区的末尾;
过程
例如 :
代码
int[] a = {5,3,8,4,2,1,13,8,4,2,1};
int n = a.length;
for (int i = 0; i < n - 1; i++) { //找到最小的
int minPos = i;
for (int j = i+1; j < n; j++) {
if (a[j]<a[minPos]){
minPos = j;
}
}
int tmp = a[i];
a[i] = a[minPos];
a[minPos] = tmp;
}
总结:
- 它并不是稳定算法:如果数组中出现相同的元素,就有可能通过交换改变顺序;
- 他肯定是原地算法
时间复杂度为O(nlogn)
O(n²)只适合小范围数据
O(nlogn)更适合大范围数据
归并排序
用到的是分治算法和递归的思路;
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
if (arr.length < 2) {
return arr;
}
int middle = (int) Math.floor(arr.length / 2);
int[] left = Arrays.copyOfRange(arr, 0, middle);
int[] right = Arrays.copyOfRange(arr, middle, arr.length);
return merge(sort(left), sort(right));
}
protected int[] merge(int[] left, int[] right) {
int[] result = new int[left.length + right.length];
int i = 0;
while (left.length > 0 && right.length > 0) {
if (left[0] <= right[0]) {
result[i++] = left[0];
left = Arrays.copyOfRange(left, 1, left.length);
} else {
result[i++] = right[0];
right = Arrays.copyOfRange(right, 1, right.length);
}
}
while (left.length > 0) {
result[i++] = left[0];
left = Arrays.copyOfRange(left, 1, left.length);
}
while (right.length > 0) {
result[i++] = right[0];
right = Arrays.copyOfRange(right, 1, right.length);
}
return result;
}
快速排序
快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。
//递归分治思路
private int[] quickSort(int[] arr, int left, int right) {
if (left<right){
int potion = partition(arr, left, right);//找到中间的指pivot
quickSort(arr,left,potion-1); //左边分区操作
quickSort(arr,potion+1,right);//右边分区操作
}
return arr;
}
private int partition(int[] arr, int left, int right) {
int pivot = left;//取第一个为pivot参照点
int index = pivot+1; //从参照点开始遍历
for (int i = index; i <= right; i++) {//开始排序,将小于pivot的放左边,大于的放右边
if (arr[i]<arr[pivot]){ //如果遍历过程中发现小于pivot的
swap(arr,i,index); //index就是大区间的开始索引
index++; //后移
}
}
swap(arr,index-1,pivot); //把pivot放中间
return index-1; //返回pivot的位置
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
时间复杂度为O(n)
桶排序
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
return bucketSort(arr, 5);
}
private int[] bucketSort(int[] arr, int bucketSize) throws Exception {
if (arr.length == 0) {
return arr;
}
int minValue = arr[0];
int maxValue = arr[0];
for (int value : arr) {
if (value < minValue) {
minValue = value;
} else if (value > maxValue) {
maxValue = value;
}
}
int bucketCount = (int) Math.floor((maxValue - minValue) / bucketSize) + 1;
int[][] buckets = new int[bucketCount][0];
// 利用映射函数将数据分配到各个桶中
for (int i = 0; i < arr.length; i++) {
int index = (int) Math.floor((arr[i] - minValue) / bucketSize);
buckets[index] = arrAppend(buckets[index], arr[i]);
}
int arrIndex = 0;
for (int[] bucket : buckets) {
if (bucket.length <= 0) {
continue;
}
// 对每个桶进行排序,这里使用了插入排序
bucket = insertSort.sort(bucket);
for (int value : bucket) {
arr[arrIndex++] = value;
}
}
return arr;
}
/**
* 自动扩容,并保存数据
*
* @param arr
* @param value
*/
private int[] arrAppend(int[] arr, int value) {
arr = Arrays.copyOf(arr, arr.length + 1);
arr[arr.length - 1] = value;
return arr;
}
计数排序
他其实是桶排序的特殊情况,如果数据的最大值是k,那么久分k个桶,这样就不用桶内的排序,最好的例子就是高考分数的排序,所以计数排序比较适合数据范围不是很大的场景;
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
int maxValue = getMaxValue(arr);
return countingSort(arr, maxValue);
}
private int[] countingSort(int[] arr, int maxValue) {
int bucketLen = maxValue + 1;
int[] bucket = new int[bucketLen];
for (int value : arr) {
bucket[value]++;
}
int sortedIndex = 0;
for (int j = 0; j < bucketLen; j++) {
while (bucket[j] > 0) {
arr[sortedIndex++] = j;
bucket[j]--;
}
}
return arr;
}
private int getMaxValue(int[] arr) {
int maxValue = arr[0];
for (int value : arr) {
if (maxValue < value) {
maxValue = value;
}
}
return maxValue;
}
基数排序
如果要排序手机号码,11位数,太大不适合用桶排序和计数排序;
基数排序是一种非比较型整数排序算法;不只是可以排序数字;
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
int maxDigit = getMaxDigit(arr);
return radixSort(arr, maxDigit);
}
/**
* 获取最高位数
*/
private int getMaxDigit(int[] arr) {
int maxValue = getMaxValue(arr);
return getNumLenght(maxValue);
}
private int getMaxValue(int[] arr) {
int maxValue = arr[0];
for (int value : arr) {
if (maxValue < value) {
maxValue = value;
}
}
return maxValue;
}
protected int getNumLenght(long num) {
if (num == 0) {
return 1;
}
int lenght = 0;
for (long temp = num; temp != 0; temp /= 10) {
lenght++;
}
return lenght;
}
private int[] radixSort(int[] arr, int maxDigit) {
int mod = 10;
int dev = 1;
for (int i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
// 考虑负数的情况,这里扩展一倍队列数,其中 [0-9]对应负数,[10-19]对应正数 (bucket + 10)
int[][] counter = new int[mod * 2][0];
for (int j = 0; j < arr.length; j++) {
int bucket = ((arr[j] % mod) / dev) + mod;
counter[bucket] = arrayAppend(counter[bucket], arr[j]);
}
int pos = 0;
for (int[] bucket : counter) {
for (int value : bucket) {
arr[pos++] = value;
}
}
}
return arr;
}
/**
* 自动扩容,并保存数据
*
* @param arr
* @param value
*/
private int[] arrayAppend(int[] arr, int value) {
arr = Arrays.copyOf(arr, arr.length + 1);
arr[arr.length - 1] = value;
return arr;
}
三种比较
基数排序:根据键值的每位数字来分配桶;
计数排序:每个桶只存储单一键值;
桶排序:每个桶存储一定范围的数值;