一、冒泡排序
1.思路
从序列头开始,两两比较,将较大值交换到后面,直到将最大值冒到最后,重复此步骤,直到序列有序。
图示:
2.时间复杂度:
最坏情况:O(n^2)–完全无序
最好情况:O(n)–序列有序
3.空间复杂度: O(1)
4.稳定性:稳定
5.代码实现
public static void bubbleSort(int[] arr) {
if(arr == null || arr.length ==1){
return;
}
boolean flag = false; // 表示是否发生交换
for (int i = 0; i < arr.length-1; i++) { // 趟数
flag = false; //每一趟都要更新flag
for (int j = 0; j < arr.length-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 = false)
return;
}
}
二、直接插入排序
1.思路
1.从第一个元素开始,该元素可以认为已经被排序
2.取出下一个元素,在已经排序的元素序列中从后向前扫描
3.如果该元素(已排序)大于新元素,将该元素移到下一位置
4.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置,将新元素插入到该位置后
图示:
2.时间复杂度
最坏情况:O(n^2)–序列无序
最好情况:O(n)–序列有序
3.空间复杂度:O(n)
4.稳定性:稳定
5.代码实现
public static void insertionSort(int[] arr){
if(arr == null || arr.length ==1){
return;
}
for(int i = 1;i<arr.length;i++){ // i遍历待排序数据 第一个数据不需排序
int temp= arr[i];
int j =0;
for(j = i-1;j>=0;j--){ //找要插入的位置,j从后往前遍历已排序的数据
if(arr[j] > temp){
//数据移动,将大的数据往后移
arr[j+1] = arr[j];
}else {
break;
}
}
arr[j+1] = temp;
}
}
三、选择排序
1.思路
从待排序序列中选择最小值,和当前 i 交换
图示:
2.时间复杂度:O(n^2)
3.空间复杂度:O(1)
4.稳定性:不稳定
5.代码实现
public static void selectSort(int[] arr){
if(arr == null || arr.length ==1){
return;
}
int i=0,j=0;
for(i = 0;i<arr.length;i++){ // 遍历
int minIndex = i; //保存i下标
for(j = i+1;j < arr.length;j++){ // 每次再遍历i后面的数据
if(arr[j] < arr[minIndex]){ // i与j的值比较
minIndex = j; // 保存较小值下标
}
}
if(i != minIndex) { // 减少交换次数
swap(arr, i, minIndex);
}
}
}
四、快速排序
1.思路
通过选择一个关键值作为基准点,比基准值小的都在左边序列(一般是无序的),比基准值大的都在右边(一般是无序的)。依次递归,达到总体待排序序列都有序。
图示:
2.时间复杂度
最好情况:O(nlog2n)
最坏情况:O(n^2)
3.空间复杂度:O(log2n)
4.稳定性:不稳定
5.快排优化:(1)随机选取基准值。(2)三数取中:取三个关键字先进行排序,将中间数作为基准, 一般是取左端、右端和中间三个数。将三个数进行排序,将中间的值放到第一个 low 位置,作为基准进行比较。(3)数据非常少的时候用冒泡法。
6.代码实现
//一次划分过程 一次排序后:【比基准小的元素】【基准数】【比基准大的元素】
public static int parttition(int[] arr,int low ,int high) {
int temp = arr[low]; // 选取第一个数为基准
while (low < high) {
//先从右往左找到第一个比基准值小的元素
while (low < high && arr[high] > temp) {
high--; //若比基准值大,继续比较下一个
}
if(low == high){
break;
}else {
arr[low] = arr[high]; //把小于基准值的元素赋值到前面的“空位”
}
//再从左往右找到第一个比基准值大的元素
while (low<high && arr[low] <= temp) {
low++; // 若比基准值小,继续比较下一个
}
if(low == high){
break;
}else {
arr[high] = arr[low];// 把大于基准值的元素赋值到后面的“空位”
}
}
arr[low] = temp; //把基准值赋值到“空位”
return low;
}
public static void quick(int[] arr,int low,int high){
int pos = parttition(arr,low,high); //得到基准值的下标
//如果左边元素 > 1
if(pos-low >1 ){
quick(arr,low,pos-1);// 递归
}
//如果右边元素 > 1
if(high-pos > 1){
quick(arr,pos+1,high);
}
}
public static void quickSort(int[] arr){
quick(arr,0,arr.length-1);
}
/**
* 快排优化
* 1.随机选取基准值
*/
public static int randomNumber(int[] arr){
Random random = new Random(); //随机选取
int index = random.nextInt(arr.length); // [0,arr.length)
return index;
}
/**
* 快排优化
* 2.三数取中
*/
public static int getNumber(int[] arr,int low,int high){
int numLeft = arr[low];
int numRight = arr[high];
int numMid = arr[(high-low)>>>1+low];
int[] brr = {numLeft,numMid,numRight};
bubbleSort(brr);
return brr[arr.length/2];
}
/**
* 快排优化
* 3.数据非常少的时候用冒泡法
*/
/*if(high - low < 100) {
bubbleSort(arr);
}*/
五、希尔排序
1.思路
即简单插入排序的优化。希尔排序是按照一定的增量进行分组排序,待排序序列有n个元素,取一个小于n的整数m1为增量,把待排序序列以间隔m1分成若干子序列,对每个子序列进行简单插入排序;再取第二个小于m1的增量m2,重复上述分组排序操作;直到取到增量为1后,排序结束。
图示:
2.时间复杂度:
最好情况:O(n)
最坏情况:O(n^2)
3.空间复杂度:O(1)
4.稳定性:不稳定
5.代码实现
public static void shell(int[] arr,int gap){ //gap为增量
for(int i = gap;i<arr.length;i++){ //从第gap个元素开始
int temp = arr[i];
int j = 0;
for(j = i-gap;j >= 0;j -= gap){ //和前第gap个元素比较
if(temp < arr[j]){
arr[j+gap] = arr[j]; //若前大则将前值赋给后值
}else {
break;
}
}
arr[j+gap] = temp;
}
}
public static void shellSort(int[] arr){
int[] group = {5,3,1}; //增量序列
for(int i = 0;i<group.length;i++){
shell(arr,group[i]);
}
}
六、归并排序
1.思路
先让每一小段有序,再让小段之间变得有序。将待排序的数列分成若干个长度为1的子数列,然后将这些数列两两合并;得到若干个长度为2的有序数列,再将这些数列两两合并;得到若干个长度为4的有序数列,再将它们两两合并;直接合并成一个数列为止。
图示:
2.时间复杂度:O(nlog2n)
3.空间复杂度:O(n)
4.稳定性:稳定
5.代码实现
public static void merge(int[] arr,int gap){
int left1 = 0; // 第一个归并段的起始下标
int right1 = left1+gap-1;// 第一个归并段的结束下标
int left2 = right1+1;// 第二个归并段的起始下标
int right2 = left2+gap-1 > arr.length-1 ? arr.length-1 : left2+gap-1; //判断是否越界
int[] brr = new int[arr.length];
int j = 0;
//有两个归并段
while(right2>=left2) {
while (left1 <= right1 && left2 <= right2) { //结束条件:左边界<=右边界
if (arr[left1] <= arr[left2]) {
brr[j++] = arr[left1++];
} else {
brr[j++] = arr[left2++];
}
}
//其中一个归并段已没有数据,另一个归并段直接拷贝下来
while (left1 <= right1) { //第一个归并段还有数据,直接拷贝下来
brr[j++] = arr[left1++];
}
while (left2 <= right2) {
brr[j++] = arr[left2++];
}
//更新归并段,循环跑起来
left1 = right2 + 1;
right1 = left1 + gap - 1;
left2 = right1 + 1; //不存在下标越界错误ArrayIndexOutOfBoundsException
right2 = left2 + gap - 1 > arr.length - 1 ? arr.length - 1 : left2 + gap - 1;
}
//没有两个归并段,单的直接拷贝下来
if(right1 >= arr.length-1){
for(int i = left1;i < arr.length;i++){
brr[j++] = arr[i];
}
}
//将brr拷贝到arr
for(int i = 0;i < brr.length;i++){
arr[i] = brr[i];
}
}
public static void mergeSort(int[] arr){
for(int i =1;i<arr.length;i*=2){ // 2个2个有序,4个4个有序
merge(arr,i);
}
}
七、堆排序
1.思路
堆是一棵顺序存储的完全二叉树。完全二叉树中所有非终端节点的值均不大于(或不小于)其左、右分支节点的值。其中每个节点的值小于等于其左、右分支的值,这样的堆称为小根堆;其中每个节点的值大于等于其左、右孩子的值,这样的堆称为大根堆。若想升序则建立大根堆,若想降序,则建立小根堆。
堆排序的原理:假设从小到大排序,先构造出来大根堆,然后取出堆顶元素(也就是最大的元素),放到数组的最后面,然后再将剩余的元素构造大根堆,再取出堆顶元素放到数组倒数第二个位置,依次类推,直到所有的元素都放到数组中,排序就完成了。
图示:
2.时间复杂度:nlog2n
3.空间复杂度:O(1)
4.稳定性:不稳定
5.代码实现
public static void adjust(int[] arr,int begin,int end){
int temp = arr[begin];
//挑选左右分支较大值,根节点=较大值
for(int i = 2*begin+1;i<=end;i = 2*i+1){ // i从根节点跑到其左分支
if(i+1 <= end && arr[i] < arr[i+1]){
i = i+1; // i保存左右分支较大值小标
}
if(arr[i] > temp){
arr[begin] = arr[i];
begin = i;
}else {
break;
}
}
arr[begin] = temp;
}
public static void heapSort(int[] arr){
//建大根堆
for(int i = (arr.length-1-1)/2;i>=0;i--){ // i代表要调整根节点的下标
adjust(arr,i,arr.length-1); // 大根堆建立好了
}
for(int i = 0;i<arr.length;i++){
int temp = arr[0];
arr[0] = arr[arr.length-1-i];
arr[arr.length-1-i] = temp;
adjust(arr,0,arr.length-1-i-1);
}
}
八、基数排序(桶排序)
1.思路
基数排序,从低位(个位)开始,根据个位数排序一次,然后根据十位数排序,再根据百位数进行排序……最终完成整个数组的排序。
对于十进制数字而言,每一位只会是 0~9 这十个数字,我们通常使用桶排序(计数排序)来完成每一位数的排序。
桶排序根据要排序的数据的个位索引下标依次入桶操作,求得最大数的最大位数,接下来依次出桶,然后继续以十位数的数字进行再次入桶操作,然后在进行出桶操作,…出桶操作,我们将其看成一个队列,进行先进先出操作。
图示:
2.时间复杂度:O(n)
3.空间复杂度:O(n)
4.稳定性:稳定
5.代码实现
public static int getMaxFigur(int[] arr){
int max=arr[0];
for(int i=1;i<arr.length;i++){
if(arr[i]>max){
max=arr[i];
}
}
int num = GetFigur(max);
return num;
}
public static int GetFigur(int n){
int count=0;
if(n==0){
return 1;
}
while(n!=0){
count++;
n/=10;
}
return count;
}
public static void redixSort(int[] arr){
int maxfigur=getMaxFigur(arr);
for(int i=0;i<maxfigur;i++){
redix(arr,i);//i 表示当前取第几个关键字
}
}
//获取 num 数字的右数第 count 个十进制数
private static int getDec(int num,int count){
for(int i=0;i<count;i++){
num/=10;
}
return num%10;
}
@SuppressWarnings("unchecked")
public static void redix(int[] arr,int figur){//figur 表示取第几个关键字
//定义是个链表头
List[] list=new LinkedList[10];//定义是个桶
for(int i=0;i<list.length;i++){
list[i]=new LinkedList();
}
for(int i=0;i<arr.length;i++){
//入桶 insert
int key=getDec(arr[i],figur);//key 表示要进第几个桶
//插入到第几号桶中
list[key].add(arr[i]);
//将桶中的数据插入到数组当中
}
int z=0;
for(int j=0;j<10;j++){//将桶中的数据先进先出的方式出到数组中
for(int i=0;i<list[j].size();i++){//桶中数据个数
arr[z++] = (int)list[j].get(i);//桶中数据依次放到数组中
}
}
}