排序算法
冒泡排序
- 时间复杂度:n*n
- 大的往后扔,后面是最先有序的(也可以往前面冒泡)
- 外层循环0 - length-1,内层循环0 - length-i-1
- i,j都从零开始
public static void bubbleSort(int[] a){
int tmp;
//第一层循环是比较轮数
for (int i = 0;i<a.length-1;i++)
{
for (int j = 0;j<a.length-i-1;j++)
{
if(a[j]>a[j+1])
{
tmp = a[j+1];
a[j+1] = a[j];
a[j] = tmp;
}
}
}
}
选择排序
- 时间复杂度:n*n
- 选择最前面的是最小的min(或者最大的)拿后面每一个跟第一个比较,有更小就把min换成那个更小的下标
- 一轮下来检查min,有变化就交换a[min],a[i]
- 检查min时候变化并交换是在j循环外面,i循环里面
- i从零开始,j从i+1开始
- 最前面的先有序(一定是的)
public static void SelectSort(int[] a){
for(int i = 0;i<a.length;i++)
{
int index = i;
for(int j = i+1;j<a.length;j++)
{
if(a[j]<a[index])
{
index = j;
}
}
if(index != i)
{
int tmp = a[i];
a[i] = a[index];
a[index] = tmp;
}
}
}
快速排序
- 时间复杂度:n*logn
- 关键是找到正确索引的位置,然后分别对索引左边(0到index-1)和右边(index+1到最后)的序列递归排序
- 索引函数getIndex(arr,low,high)返回值是int
- 索引函数:
- 先保存low处的值到tmp
- 当右边high处值一直比tmp(索引)大时,high–左移,等到high值不大的时候停止,并把右值赋值给左值
- 当low处的值一直比tmp(索引小的时候),low++右移。等到low不再小的时候停止,并把左值赋值给右值
- 先右边左移,并把右值给左值。在左边右移,并把左值给赋给右值。最后保存索引和索引处的值。
public class QuickSort {
private static void QuickSort(int[] arr,int low,int high) {
if(low<high) {
int index = getIndex(arr,low,high);
QuickSort(arr,0,index-1);
QuickSort(arr,index+1,high);
}
}
private static int getIndex(int[] arr,int low,int high) {
int tmp = arr[low];
while(low<high){
while(low<high && arr[high]>tmp){
high--;
}
arr[low] = arr[high];
while(low<high && arr[low]<tmp){
low++;
}
arr[high] = arr[low];
}
arr[low] = tmp;
return low;
}
public static void main(String[] args){
int[] arr = {1,3,5,7,9,2,4,6,8,10};
QuickSort(arr,0,9);
for(int i = 0;i<arr.length;i++){
System.out.println(arr[i]);
}
}
}
插入排序
- 时间复杂度:O(n^2)
- 先认为第一个元素是排好序了,数组分为已经排好序的和未排好序的,已经排好序的在左边,每次拿没排好序的第一个和未排好序的数进行比较
- 拿当前元素i跟他前一个元素(i-1)比较,记下当前值为tmp
- 内层循环j是从i的前一个开始(也就是排好序的最后一个数),当j大于0并且j处值比tmp大时,都要把已完成序列往后移(腾位置插入)
- 最后把tmp放入a[j+1]处
private static void InsertSort(int[] arr){
int tmp;
for(int i = 1;i<arr.length;i++){
//未排序的比排好序的末尾小,能插入
if(arr[i]<arr[i-1]){
tmp = arr[i];
int j;
//循环条件是arr[j]>tmp,而不是arr[j]>arr[j+1],j+1每次被覆盖了
for(j = i-1;j>0 && arr[j]>tmp;j--){
arr[j+1] = arr[j];
}
//跳出循环j仍然--,所以插入j+1处
arr[j+1] = tmp;
}
}
}
希尔排序
- 时间复杂度:n*logn
- 最外层是分组次数,每次折半
- 内部双层就是插入排序,先从拍好续的下一个位置开始循环(gap),第一组,第二组,所以是i++
- j就是排好序的最后一个位置,比较j和tmp(实际就是g+gap),然后j每次向左缩小
- 最后j+gap就是插入的位置,类似插入排序的j+1
private static void InsertSort(int[] arr){
int tmp;
for(int i = 1;i<arr.length;i++){
if(arr[i]<arr[i-1]){
tmp = arr[i];
int j;
//循环条件是arr[j]>tmp,而不是arr[j]>arr[j+1],j+1每次被覆盖了
for(j = i-1;j>0 && arr[j]>tmp;j--){
arr[j+1] = arr[j];
}
//跳出循环j仍然--,所以插入j+1处
arr[j+1] = tmp;
}
}
}
public static void ShellSort(int[] arr) {
int d = arr.length;
int tmp;
int j;
for (int gap = d / 2; gap > 0; gap /= 2) {
//里面的部分就是插入排序
for (int i = gap; i < d; i++) {
tmp = arr[i];
for (j = i - gap; j > 0 && arr[j] > tmp; j = j - gap) {
arr[j + gap] = arr[j];
}
arr[j + gap] = tmp;
}
}
}
堆排序
- 时间复杂度:nlogn(要构造堆,不适合数值少的排序)
- 堆是一颗完全二叉树,这是他的结构性
- 最大堆:每个节点都大于等于他的左右孩子节点(最大堆的根是最大值),最小堆相反
- 第一次先从(arr.length-1)/2往回循环构造堆
- 然后循环交换a[0]和最后,从根开始往下调整堆
- 伪代码
- 先从(arr.length-1)/2往回循环,用heapAdjust把每个节点调成符合大顶堆
- (从最后一个元素开始和跟交换,然后继续把前n-1调成大顶堆)循环
- heapAdjust函数,先左右节点比大小,在拿大的和父节点比
import javax.swing.*;
public class HeapSort {
private static void heapSort(int[] arr) {
//从(arr.length - 1)/2开始往回倒,检查是否为大顶堆
for (int i = (arr.length - 1) / 2; i >= 0; i--) {
heapAdjust(arr, i, arr.length);
}
//逐步将每个最大值和末尾的元素交换,并再次调整大顶堆
for (int i = arr.length - 1; i > 0; i--) {
//i初值是长度减一,就是下标
swap(arr, 0, i);
//第三个参数是要处理的堆的长度,而i初值就是长度减一
heapAdjust(arr, 0, i);
}
}
/**
* 调整堆
* @param arr 需要排序的数组
* @param i 堆的根节点
* @param n 数组的长度
* 调整堆函数在调整了父节点和子节点后会一路不稳定的那一边下面继续调整(比如交换了父节点和左子节点,那么就会继续调整左子节点和左子节点的子节点)
*/
private static void heapAdjust(int[] arr, int i, int n) {
int child;
int father;
//i落在要交换的孩子节点处,稳不稳定是在这个孩子节点后面
for (father = arr[i]; leftChild(i) < n; i = child) {
child = leftChild(i);
//先比较左子树和右子树,右子树大则child++,否则比较左子树和父节点大小
if (child != n - 1 && arr[child] < arr[child + 1]) {
child++;
}
if (father < arr[child]){
arr[i] = arr[child];
}
else{
break;
}
}
//能走到这表示前面把大的子节点赋值给了父,这里要把原先的父节点的值赋值给子节点
arr[i] = father;
}
/**
* 获取左孩子节点的下标
*/
private static int leftChild(int i) {
return 2 * i + 1;
}
/**
* 交换元素函数
*/
private static void swap(int[] arr, int index1, int index2) {
int temp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = temp;
}
public static void main(String[] args){
int[] arr ={1,3,5,7,9,2,4,6,8,10};
heapSort(arr);
for(int i =0;i<arr.length;i++){
System.out.println(arr[i]);
}
}
}
归并排序
- 时间复杂度:nlogn
- 先分解在合并,分成一个一个,在合并,
- 最后剩两组的时候,每组第一个比,小的先放到新数组,然后少一个的组的标记后移
- 奇数分组时,前面多后面少
import java.util.*;
public class MergeSort {
//二路归并,两个排好序的子序列合并为一个子序列.合并函数merge
//mid是左边数组结尾
public void merge(int[] a,int left,int mid,int right){
//p1,p2是检测指针,p3是存放指针
int [] tmp = new int[a.length];
//p1是要合并的左边分组第一个,p2是要合并的右边分组的第一个元素1
//k是存放指针
int p1 = left;
int p2 = mid+1;
int k = left;
//有一边已经全合并进新数组了就停
while (p1 <= mid && p2 <= right){
if(a[p1] <= a[p2]){
tmp[k++] = a[p1++];
}else {
tmp[k++] = a[p2++];
}
}
//第一个序列没有全检查完,后面的直接全加到合并数组后
while (p1 <= mid){
tmp[k++] = a[p1++];
}
//第二个序列没有检查完
while (p2<=right){
tmp[k++] = a[p2++];
}
//temp数组是从left开始存临时数据的,用的<=right。right也要存进去
for(int i = left;i<=right;i++){
a[i] = tmp[i];
}
}
public void mergeSort(int[] a,int start,int end){
//当序列只有一个元素时结束递归
if(start<end){
//mid位置在分组左边的最后一个位置
int mid = (start+end)/2;
mergeSort(a,start,mid);
mergeSort(a,mid+1,end);
merge(a,start,mid,end);
}
}
public static void main(String[] args){
int[] a = {1,3,5,7,9,10,8,6,4,2};
MergeSort sort = new MergeSort();
sort.mergeSort(a,0,a.length-1);
for(int i = 0;i<a.length;i++){
System.out.println(a[i]);
}
}
}
快排最差情况
数组已经是正序或者倒序时候出现最差情况,(就相当于枢轴每次只能把数据分到一边,另一边没有数)
冒泡排序
最坏是逆序