冒泡排序
冒泡排序的基本思想是:通过对待排序序列从前往后(从小标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使得值较大的元素逐渐从前往后移动,就像水底下的气泡一样逐渐向上冒。
优化:因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换就说明序列有序。因此要在排序过程中设置一个flag判断元素是否进行过交换,从而减少不必要的比较
冒泡排序的特点:
- 长度为n的数组需要(n-1)次循环
- 每一趟排序的次数逐渐减少
public class BubbleSort {
public static void main(String[] args) {
//定义一个数组
int[] arr = {6,12,9,-3,2};
//变量用于交换数据
int temp = 0;
//外层循环控制比较次数
for (int i = 0;i < arr.length-1;i++){
//内层循环遍历要比较的元素
for (int j = 0;j<arr.length-1-i;j++){
if (arr[j] > arr[j+1]){
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
System.out.println("第"+(i+1)+"趟排序后:");
System.out.println(Arrays.toString(arr));
}
}
}
/*输出结果
第1趟排序后:
[6, 9, -3, 2, 12]
第2趟排序后:
[6, -3, 2, 9, 12]
第3趟排序后:
[-3, 2, 6, 9, 12]
第4趟排序后:
[-3, 2, 6, 9, 12]
*/
代码优化
public class BubbleSort {
public static void main(String[] args) {
//定义一个数组
int[] arr = {6,12,9,-3,2};
//变量用于交换数据
int temp = 0;
//外层循环控制比较次数
for (int i = 0;i < arr.length-1;i++){
//标识变量
boolean flag = false;
//内层循环遍历要比较的元素
for (int j = 0;j<arr.length-1-i;j++){
if (arr[j] > arr[j+1]){
//如果有交换,设为true
flag = true;
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
//flag为false,表明没有数据交换,排序已完成
if (!flag)
break;
System.out.println("第"+(i+1)+"趟排序后:");
System.out.println(Arrays.toString(arr));
}
}
}
/*输出结果
第1趟排序后:
[6, 9, -3, 2, 12]
第2趟排序后:
[6, -3, 2, 9, 12]
第3趟排序后:
[-3, 2, 6, 9, 12]
*/
选择排序
基本思想:第一次从arr[0]-arr[n-1]中选取最小值,与arr[0]交换;第二次从arr[1]-arr[n-1]中选取最小值,与arr[1]交换;第三次从arr[2]-arr[n-1]中选取最小值,与arr[2]交换,…,第n-1次从arr[n-2]-arr[n-1]中选出最小值,与arr[n-2]交换,总共通过n-1次得到一个从小到大排序的有序序列。
public class SelectSort {
public static void main(String[] args) {
int[] arr = {8,7,23,14,2};
//记录最小值的下标
int minIndex;
//记录最小值
int min;
//外层循环控制排序次数
for (int i = 0;i < arr.length-1;i++){
//把本趟排序的第一个数假设为最小值
minIndex = i;
min = arr[i];
for (int j = i+1;j<arr.length;j++){
if (min > arr[j]){
min = arr[j];
minIndex = j;
}
}
//如果找到比假定值还小的数,交换
if (minIndex != i){
arr[minIndex] = arr[i];
arr[i] = min;
}
System.out.println("第"+(i+1)+"趟排序后:");
System.out.println(Arrays.toString(arr));
}
}
}
/*
第1趟排序后:
[2, 7, 23, 14, 8]
第2趟排序后:
[2, 7, 23, 14, 8]
第3趟排序后:
[2, 7, 8, 14, 23]
第4趟排序后:
[2, 7, 8, 14, 23]
*/
直接插入排序
直接插入排序属于内部排序,是对于欲排序的元素以插入的方式找寻该元素的适当位置以达到排序的目的。其基本思想是:
把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只有一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码一次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
public class InsertSort {
public static void main(String[] args) {
int[] arr = {32,56,12,24,5,15};
insertSort(arr);
}
static void insertSort(int[] arr){
int insertVal;
int insertIndex;
for (int i = 1;i<arr.length;i++){
insertVal = arr[i];
insertIndex = i -1;
while (insertIndex >= 0&&insertVal < arr[insertIndex]){
arr[insertIndex+1] = arr[insertIndex];
insertIndex--;
}
//退出while循环,说明位置已经找到
arr[insertIndex+1] = insertVal;
System.out.println("第"+i+"轮排序:");
System.out.println(Arrays.toString(arr));
}
}
}
/**
第1轮排序:
[32, 56, 12, 24, 5, 15]
第2轮排序:
[12, 32, 56, 24, 5, 15]
第3轮排序:
[12, 24, 32, 56, 5, 15]
第4轮排序:
[5, 12, 24, 32, 56, 15]
第5轮排序:
[5, 12, 15, 24, 32, 56]
*/
n个数据要进行n-1次排序
希尔排序
希尔排序又叫做缩小增量排序,是直接插入排序的更高效版本。基本思想是:把记录按下标的一定增量分组,对每组使用直接插入排序的算法进行排序。随着增量的逐渐减少,每组包含的关键词越来越多,当增量减至1,整组数据就分为一组,算法终止。
在直接插入排序中,如果出现{2,3,4,5,6,1}这样的序列,要想从小到大排序,只需将1直接放到第一个位置即可,但是也要逐次比较到最后,效率很低,这时可用希尔排序替代。
public class InsertDemo02 {
public static void main(String[] args) {
int[] arr = new int[] {8,9,1,7,2,3,5,4,6,0};
System.out.println("初始序列"+Arrays.toString(arr));
shellSort(arr);
}
public static void shellSort(int[] arr){
int cnt = 0;
//遍历所有步长
for(int d = arr.length/2;d > 0;d/=2) {
//遍历所有元素
for (int i = d; i < arr.length; i++) {
//遍历本组中所有的元素
for (int j = i - d; j >= 0; j -= d) {
//如果当前元素大于加上步长的那个元素
if (arr[j] > arr[j + d]) {
int temp = arr[j];
arr[j] = arr[j + d];
arr[j + d] = temp;
}
}
}
System.out.println("第"+(++cnt)+"轮排序后:"+Arrays.toString(arr));
}
}
}
/**
初始序列[8, 9, 1, 7, 2, 3, 5, 4, 6, 0]
第1轮排序后:[3, 5, 1, 6, 0, 8, 9, 4, 7, 2]
第2轮排序后:[0, 2, 1, 4, 3, 5, 7, 6, 9, 8]
第3轮排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
*/
上面实现的希尔排序中对数据的处理采用交换的方式,整体的效率是不高的,当数据是几万个的时候,它的效果甚至不如直接插入排序。将交换的部分进行优化,将它改成直接插入排序的移位法:
public class InsertDemo02 {
public static void main(String[] args) {
int[] arr = new int[] {8,9,1,7,2,3,5,4,6,0};
System.out.println("初始序列"+Arrays.toString(arr));
shellSort1(arr);
}
public static void shellSort1(int[] arr){
int cnt = 0;
int insertIndex;
int temp;
//遍历所有步长
for(int d = arr.length/2;d > 0;d/=2) {
//遍历所有元素
for (int i = d; i < arr.length; i++) {
insertIndex = i;
temp = arr[insertIndex];
if (arr[insertIndex] < arr[insertIndex-d]) {
while (insertIndex-d >= 0 && temp < arr[insertIndex - d]) {
arr[insertIndex] = arr[insertIndex - d];
insertIndex -= d;
}
arr[insertIndex] = temp;
}
}
System.out.println("第"+(++cnt)+"轮排序后:"+Arrays.toString(arr));
}
}
}
/**
初始序列[8, 9, 1, 7, 2, 3, 5, 4, 6, 0]
第1轮排序后:[3, 5, 1, 6, 0, 8, 9, 4, 7, 2]
第2轮排序后:[0, 2, 1, 4, 3, 5, 7, 6, 9, 8]
第3轮排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
*/
快速排序
快速排序是对冒泡排序的一种改进,基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对两部分数据分别进行快速排序,整个排序过程可以用递归进行。
public class QuickSort {
public static void main(String[] args) {
int[] arr={-9,12,0,-32,78,1};
quickSort(arr,0,arr.length-1);
System.out.println("排序后:"+ Arrays.toString(arr));
}
public static void quickSort(int[] arr,int start,int end){
//当start==end时递归结束
if(start < end){
//把数组中的第0个数作为标准数
int standard = arr[start];
//记录需要排序的下标
int low = start;
int high = end;
//循环找标准数大的数和比标准数小的数
while(low < high) {
//右边的数字比标准数大
while(low < high && arr[high] >= standard) {
high--;
}
//使用右边的数字替换左边的数字
arr[low] = arr[high];
//左边的数字比标准数小
while(low < high && arr[low] <= standard){
low++;
}
//左边的数字替换右边的数字
arr[high] = arr[low];
//当low == high时
arr[low] = standard;
//处理所有小的数字
quickSort(arr,start,low);
//处理所有大的数字
quickSort(arr,low+1,end);
}
}
}
}
/**
排序后:[-32, -9, 0, 1, 12, 78]
*/
归并排序
归并排序是利用归并的思想实现的排序方法,该算法采用经典的分治策略(分治法将问题分成一些小问题然后递归求解,而治的阶段则将分的阶段得到的各答案“修补”在一起,即分而治之)。
public static void main(String[] args) {
int[] arr = new int[] {3,6,1,9,3,2};
System.out.println(Arrays.toString(arr));
mergeSort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void mergeSort(int[] arr,int low,int high){
int middle = (high + low)/2;
if(low < high) {
//处理左边
mergeSort(arr, low, middle);
//处理右边
mergeSort(arr, middle + 1, high);
//归并
merge(arr, low, middle, high);
}
}
public static void merge(int[] arr,int low,int middle,int high){
//用于存储归并后的临时数组
int[] temp = new int[high - low +1];
//记录第一个数组中需要遍历的下标
int i = low;
//记录第二个数组中需要遍历的下标
int j = middle + 1;
//用于记录在临时数组中存放的下标
int index = 0;
//遍历两个数组取出小的数字,放入临时数组中
while (i <= middle && j <= high){
//第一个数组的数据更小
if (arr[i] <= arr[j]){
//把小的数据放入临时数组中
temp[index] = arr[i];
//让下标向后移一位
i++;
}else{
temp[index] = arr[j];
j++;
}
index++;
}
//处理多余的数据
while(j <= high){
temp[index] = arr[j];
j++;
index++;
}
while (i <= middle){
temp[index] = arr[i];
i++;
index++;
}
//把临时数组中的数据重新存入原数组
for(int k = 0;k < temp.length;k++){
arr[k+low] = temp[k];
}
}
基数排序
基数排序属于“分配式排序”,又称“桶子法”,通过键值的各个位的值,将要排序的元素分配到某些桶中,达到排序的目的。
public class RadixSortDemo1 {
public static void main(String[] args) {
int[] arr = {32,34,12,3,21,15,413,124};
radixSort(arr);
}
//基数排序方法
public static void radixSort(int[] arr){
//得到数组中最大的数
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (max < arr[i])
max = arr[i];
}
//得到最大数是几位数
int maxLength = (max + "").length();
//定义一个二维数组,表示10个桶,每个桶就是一个一维数组
//基数排序使用空间换时间
int[][] bucket = new int[10][arr.length];
//记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
//比如bucketElementCounts[0],记录的是bucket[0]这个桶放入数据的个数
int[] bucketElementCounts = new int[10];
for (int i = 0,n=1;i < maxLength;i++,n*=10){
//对每个元素的对应位进行排序处理,第一次是个位,第二位是十位...
for (int j = 0;j < arr.length;j++){
//取出每个元素的对应位的值
int digitOfElement = arr[j]/n % 10;
//放入到对应的桶中
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
bucketElementCounts[digitOfElement]++;
}
//按照一维数组的下标依次取出数据,放入到原来的数组
int index = 0;
//遍历每一个桶,并将桶中的数据放入到原来数组
for (int k = 0; k < bucketElementCounts.length; k++) {
//如果桶中有数据
if (bucketElementCounts[k] != 0){
//循环该桶即第k个桶(第k个一维数组),放入
for (int l = 0; l < bucketElementCounts[k]; l++) {
arr[index++] = bucket[k][l];
}
}
//第i+1轮处理后需要将每个bucketElementCounts[k]=0,即清空
bucketElementCounts[k] = 0;
}
System.out.println("第"+(i+1)+"轮排序后:"+ Arrays.toString(arr));
}
}
}
/*
第1轮排序后:[21, 32, 12, 3, 413, 34, 124, 15]
第2轮排序后:[3, 12, 413, 15, 21, 124, 32, 34]
第3轮排序后:[3, 12, 15, 21, 32, 34, 124, 413]
*/
说明:基数排序是对传统桶排序的扩展,速度很快,可以比快速排序的速度快,是经典的空间换时间的方式,占用内存空间很大(有10个桶,每个桶的长度和 原来数组一样,也就是有11个和原来数组一样大的数组)。当对海量数据排序时容易造成OutOfMemoryError。
|排序算法|平均时间复杂度|最好情况|最坏情况|空间复杂度|稳定性
排序算法 | 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n2) | O(n) | O(n2) | O(1) | 稳定 |
选择排序 | O(n2) | O(n2) | O(n2) | O(1) | 不稳定 |
直接插入排序 | O(n2) | O(n) | O(n2) | O(1) | 稳定 |
希尔排序 | O(nlogn) | O(nlog2n) | O(nlog2n) | O(1) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
快速排序 | O(nlogn) | O(nlogn) | O(n2) | Ologn) | 不稳定 |
基数排序 | O(n×k) | O(n×k) | O(n×k) | O(n+k) | 稳定 |
时间复杂度:一个算法执行所耗费的时间
空间复杂度:运行完一个程序需要的内存大小
n:数据规模
k:桶的个数