排序算法:
- 内部排序:值将需要处理的数据加载到内存进行排序
- 插入排序
- 直接插入排序
- 希尔排序
- 选择排序
- 简单选择排序
- 堆排序
- 交换排序
- 冒泡排序
- 快速排序
- 归并排序
- 基数排序(桶排序)
- 插入排序
- 外部排序:数据量过大,必须借助外存
算法时间复杂度:
-
时间频度:一个算法中的语句执行次数称为语句频度或时间频度T(n)
-
时间复杂度:O(1) <O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(nk)<O(2n)<O(n!)
-
常数阶O(1):无论代码多少行,只要没有循环就是常数阶
-
对数阶O(logn):
int i = 1; while (i<n){ i = i * 2; //乘以2就是2为底的对数log2n }
-
线性阶O(n):
for(int i = 0;i < n;i++){ //循环n次就是O(n) j = i; j++; }
-
线性对数阶O(nlogn):
for(int i = 0;i < n;i++){ //循环n次就是n倍的对数阶 while (i<n){ i = i * 2; //乘以2就是2为底的对数log2n } }
-
平方阶O(n^2):
for(int i = 0;i < n;i++){ //循环n的平方次 for(int j = 0;j < n;j++){ } }
-
平均时间复杂度和最坏时间复杂度:
排序法 | 平均时间 | 最差时间 | 备注 |
---|---|---|---|
冒泡 | O(n^2) | O(n^2) | |
交换 | O(n^2) | O(n^2) | |
选择 | O(n^2) | O(n^2) | |
插入 | O(n^2) | O(n^2) | |
基数 | O(log_R^B) | O(log_R^B) | |
希尔 | O(nlogn) | O(n^s) 1<s<2 | |
快速 | O(nlogn) | O(n^2) | |
归并 | O(nlogn) | O(nlogn) | |
堆 | O(nlogn) | O(nlogn) |
算法空间复杂度:该算法占用的存储空间
冒泡排序
从前向后,依次比较相邻元素的值,若发现逆序则交换,使值较大的元素依次向后移动。
优化:如果在某次排序过程中没有发生一次交换,说明已经排好序
import java.util.Arrays;
import java.util.Date;
import java.text.SimpleDateFormat;
class Eg415MathDemo
{
public static void main(String[] args)
{
int[] a = {16,25,9,90,23};
//排序之前的时间
Date date1 = new Date();
//原时间格式
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//格式化日期对象
String str = simpleDateFormat.format(date1);
System.out.println(str);
//冒泡排序
bubble(a);
//排序之后的时间
Date date2 = new Date();
//格式化日期对象
String str2 = simpleDateFormat.format(date1);
System.out.println(str2);
}
public static void bubble(int[] a){
System.out.println("排序之前的数组:");
printArray(a);
boolean flag = false; //优化标识变量,标识是否发生交换
for (int i = 0;i < a.length - 1 ;i++ ) //时间复杂度O(n^2)
{
for (int j = 0;j < a.length -1-i ;j++ )
{
if (a[j]>a[j+1])
{
flag = true;
int temp = a[j+1];
a[j+1] = a[j];
a[j] = temp;
}
}
if (flag = false) //(优化)在一趟排序中,一次都没有交换过
{
break;
}else
flag = false; //重置为false,进行下次判断
}
System.out.println("\n排序之后的数组:");
//printArray(a);
//System.out.println(arr2String(a));
//第三种输出方式
System.out.println(Arrays.toString(a)); //将数组转成字符串
}
public static void printArray(int[] arry){
for (int i = 0;i < arry.length ;i++ )
{
System.out.print(arry[i]+"\t");
}
}
//第二种输出数组模式
public static String arr2String(int[] arry) {
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0;i < arry.length;i++ )
{
if (i == arry.length - 1) //最后一个
{
sb.append(arry[i]).append("]");
}else
sb.append(arry[i]).append(",");
}
return sb.toString(); //将StringBuilder类型转换为String类型
}
}
选择排序
class Eg418SelectionSort
{
public static void main(String[] args)
{
System.out.println("排序之前的数组:");
int[] pData = {67,88,99,15,3,56,1,78,63};
print(pData);
System.out.println("\n排序之后的数组:");
selectionSort(pData,pData.length);
print(pData);
}
public static void print(int[] c){
for (int i = 0;i < c.length ;i++ )
{
System.out.print(c[i]+"\t");
}
}
public static void selectionSort(int[] data,int size){
int i = 0,j = 0,min = 0; //min假设为最小值数组元素的下标值
for (i=0;i < size -1 ;i++ )
{
min = i;
for (j = i + 1;j < size ;j++ )
{
if (data[j] < data[min]) //把大的往后移
{
min = j;
}
}
swap(data,min,i);
}
}
public static void swap(int[] x,int c,int d){
int temp = x[d];
x[d] = x[c];
x[c] = temp;
}
}
选择排序时间会比冒泡排序短
插入排序
把待排序的数组看成一个有序表和一个无序表,开始时有序表只有一个元素(第一个元素),无序表中有n-1个元素。每次从无序表中取一个元素和有序表进行比较,插入到有序表的位置。
import java.util.Arrays;
class InsertSort
{
public static void main(String[] args)
{
int[] arr ={34,1,23,2};
System.out.println("排序之前的数组;");
System.out.println(Arrays.toString(arr)); //将数组转成字符串
insertSort(arr);
}
public static void insertSort(int[] arr){
for (int i = 1;i < arr.length ;i++ ) //从1开始,因为从第二个元素开始与前一个比较
{
int indexval = arr[i]; //记录当前与前面元素相比较的值
int index = i -1; //当前元素前面数的下标
while (index >= 0 && indexval < arr[index]) //当前元素比前一个元素小
{
arr[index+1] = arr[index]; //把前一个元素向后移
index--; //index向前移继续与前面的数字(有序的)相比较
}
arr[index+1] = indexval; //因为while循环中index--,所以需要加1,将元素赋值到新的位置
}
System.out.println("排序之后的数组;");
System.out.println(Arrays.toString(arr));
}
}
希尔排序
希尔排序是对插入排序的改进,分组缩小增量排序
import java.util.Arrays;
class ShellSort
{
public static void main(String[] args)
{
int[] arr = {8,9,1,7,2,3,5,4,6,0};
System.out.println(Arrays.toString(arr));
shell2(arr);
}
//希尔排序交换式(效率低)
public static void shell(int[] arr){
int temp = 0; //用于交换数据
int count = 0; //用于统计分组的数目
for (int gap = arr.length/2;gap>0 ;gap/=2 ) //每一次分组,组数为gap
{
for(int i = gap;i<arr.length;i++){ //遍历各个组共gap组
for(int j = i-gap;j >=0;j -= gap){ //遍历组中的元素,步长为gap
if (arr[j] > arr[j+gap]) //如果当前元素大于加上步长后的元素要交换
{
temp = arr[j+gap];
arr[j+gap] = arr[j];
arr[j] = temp;
}
}
}
System.out.println("第"+(++count)+"轮:"+Arrays.toString(arr));
}
}
//希尔排序移位法(效率高)
public static void shell2(int[] arr){
int count = 0;
for (int gap = arr.length/2;gap>0 ;gap/=2 )
{
for(int i=gap;i<arr.length;i++){
//插入排序
int indexval = arr[i]; //记录当前与前面元素相比较的值
int index = i -gap; //当前元素前面数的下标
if (arr[i] < arr[index])
{
while (index >= 0 && indexval < arr[index]) //当前元素比前一个元素小
{
arr[index+gap] = arr[index]; //把前一个元素向后移
index -=gap; //index向前移继续与前面的数字(有序的)相比较
}
arr[index+gap] = indexval; //因为while循环中index-gap,所以需要加gap,将元素赋值到新的位置
}
}
System.out.println("第"+(++count)+"轮:"+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]
快速排序
import java.util.Arrays;
class QuickSort
{
public static void main(String[] args)
{
int[] arr = {55,44,33,22,11};
quickSort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void quickSort(int[] arr,int left,int right){
int l = left;
int r = right;
int pivot = arr[(left + right)/2]; //中轴值
int temp = 0; //用于交换
while (l < r) //让比pivot小的值放到左边,比pivot大的放到右边
{
while (arr[l] < pivot) //找比pivot大的,直到找到就退出
{
l += 1; //将l向左移
}
while (arr[r] > pivot) //找比pivot小的,找到就退出
{
r -= 1; //将r向右移
}
if (l >= r) //必须要有这行,否则数组下标越界
{
break;
}
//交换
temp = arr[r];
arr[r] = arr[l];
arr[l] = temp;
//如果交换后发现arr[l] == pivot,那么就将r--才能不断将=pivot的到最后靠近pivot,下一轮可以将该值放右边,避免死循环
if (arr[l] == pivot)
{
r -= 1;
}
if (arr[r] == pivot) //反之
{
l += 1;
}
}
//如果l == r;必须l++,r--,否职栈溢出
if (l == r)
{
l += 1;
r -= 1;
}
//向左递归
if(left < r){
quickSort(arr,left,r);
}
//向右递归
if(right > l){
quickSort(arr,l,right);
}
}
}
归并排序
采用归并思想实现排序,使用分治算法
package calculate;
import java.util.Arrays;
public class MergeSort {
public static void main(String[] args) {
int[] arr = {8,4,5,7,1,3,6,2}; //merge7次
int[] temp = new int[arr.length];
mergeSort(arr, 0, arr.length-1, temp);
System.out.println(Arrays.toString(arr));
}
//分+合
public static void mergeSort(int[] arr,int left,int right,int[] temp) {
if (left < right) {
int mid = (left + right)/2;
//向左递归分解
mergeSort(arr, left, mid, temp);
//向右递归分解
mergeSort(arr, mid+1, right, temp);
//合并
merge(arr, left, mid, right, temp);
}
}
//合并
/**
* @param arr 排序的原始数组
* @param left 左边有序的序列的初始索引
* @param mid 中间索引
* @param right 右边索引
* @param temp 中转数组
*/
public static void merge(int[] arr,int left,int mid,int right,int[] temp) {
int i = left; //初始化i,左边有序序列的初始索引
int j = mid + 1; //右边序列初始索引(mid在左边序列的最后一个)
int t = 0; //t是指向temp数组的当前索引,列号
//1.先把左右两边的数据(有序)按照规则填充到temp,直到左右两边有一边数据处理完毕为止
while (i <= mid && j <= right) {
//如果左边有序序列当前元素小于等于右边序列的当前元素
//将当前元素拷贝到temp数组,然后t后移,i后移
if (arr[i] < arr[j]) {
temp[t] = arr[i];
t += 1;
i += 1;
}else {//反之,将右边序列当前元素填充到temp数组
temp[t] = arr[j];
t += 1;
j += 1;
}
}
//2.把有剩余的数据一边依次全部填充到temp
while (i <= mid) { //说明左边存在剩余元素
temp[t] = arr[i];
t += 1;
i += 1;
}
while (j <= right) { //说明右边存在剩余元素
temp[t] = arr[j];
t += 1;
j += 1;
}
//3.将temp数组的元素拷贝到arr,并不是每次拷贝所有的
t = 0;
int templeft = left;
//第一次合并templeft = 0,right = 1,如图中4和8
//第二次合并templeft = 2,right = 3,如图中5和7
//第三次合并templeft = 0,right = 3,如图中4,5,7,8
//最后一次合并templeft = 0,right = 7
while (templeft <= right) {
arr[templeft] = temp[t];
t += 1;
templeft += 1;
}
}
}
基数排序
基数排序属于分配式排序,桶排序的扩展,稳定性的排序(使用空间换时间)
将所有待比较数值统一为同样的数位长度,数位较短的数前面补零,然后从最低位开始,依次进行一次排序,这样从最低位排序一直到最高位排序完成,完成以后就是有序数列
负数就不用基数排序
package calculate;
import java.util.Arrays;
public class RadixSort {
public static void main(String[] args) {
int[] arr = {53,3,542,748,14,214};
//第一轮对个位
radix(arr);
}
//基数排序(使用空间换时间)
public static void radix(int[] arr) {
//根据前面推导出基数排序
//1.得到数组中最大数的位数
int max = arr[0]; //假设第一个数就是最大的
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
//最大数几位
int maxLength = (max + "").length(); //转换为字符串
//定义二维数组表示10个桶,每个桶是一维数组,防止数据溢出定成最大的
int[][] bucket = new int[10][arr.length];
//为了记录每个桶实际存放多少个数据,定义一个一维数组记录各个桶每次放入数据的个数
int[] bucketElementCounts = new int[10];
//循环轮数(k次循环,n为了取各个位)
for (int k = 0,n=1; k < maxLength; k++,n *=10) {
for (int i = 0; i < arr.length; i++) {
//取出每个元素的位
int digitOfElement = arr[i] /n %10;
//放到对应的桶
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[i];
bucketElementCounts[digitOfElement]++; //从0依次递增
}
//按照桶的顺序(一维数组的下标依次取出数据,放入原来数组)
int index = 0;
//遍历每一个桶,放入到原数组
for (int i = 0; i < 10; i++) {
//如果桶中有数据,才放入原数组
if (bucketElementCounts[i] != 0) { //有数据
for (int j = 0; j < bucketElementCounts[i]; j++) {
//取出元素放到arr
arr[index] = bucket[i][j];
index++;
}
}
//每一轮排序后将bucketElementCounts[i]=0
bucketElementCounts[i] = 0;
}
System.out.println("第"+(k+1)+"轮"+Arrays.toString(arr));
}
}
}
//输出
第1轮[542, 53, 3, 14, 214, 748]
第2轮[3, 14, 214, 542, 748, 53]
第3轮[3, 14, 53, 214, 542, 748]