八大排序分析及代码
- 冒泡排序
- 思想及规则
- 代码
- 选择排序
- 思想及规则
- 代码
- 插入排序
- 思想
- 代码
- 希尔排序
- 思想
- 代码
- 快速排序
- 思想
- 代码
- 归并排序
- 思想
- 代码
- 基数排序
- 思想
- 代码(包含对负数的排序)
- 堆排序
- 思想
- 代码
- 总结(时间复杂度比较)
冒泡排序
每次比较相邻的两个数,如果逆序则交换 依次找出最大的
思想及规则
规则:
- 一共进行数组的大小-1次大的循环
- 每一趟排序的次数在逐渐减少
- 如果在某此次排序中,没有发生一次交换,则可以提前结束冒泡。
时间复杂度:O(n^2)
代码
//冒泡排序
public static int[] bubble(int[] arr) {
int temp = 0;
for (int i = 0; i < arr.length - 1; i++) {
boolean flag = true;
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;
flag = false;
}
}
System.out.println("这是第" + (i + 1) + "次排序");
if (flag) {
break;
}
}
return arr;
}
选择排序
思想及规则
思想:第一次从arr[0]–arr[n]找最小的,与第一个交换;
第二次从arr[1]–arr[n]中找最小的,与下标为1的交换
以此类推,直到把整个数组遍历完。
说明:
1.选择排序一共有数组大小-1轮排序
2.每1轮排序,又是一个循环,
2.1 先假定当前这个数是最小数
2.2 然后和后面的每个数进行比较,如果发现有比当前数更小的数,就重新确定最小的数,
并得到下标
2.3 当遍历到数组的最后时,就得到本轮最小的数和下标
2.4交换
代码
//选择排序
public static int[] select(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
int min = arr[i];
for (int j = i + 1; j < arr.length; j++) {
if (min > arr[j]) { //说明假定的值并不是最小的值
min = arr[j]; //重置最小值和下标
minIndex = j;
}
}
//将最小的值放在arr[i]位置上 与当前下标交换
if (minIndex != i) {
arr[minIndex] = arr[i];
arr[i] = min;
}
}
return arr;
}
插入排序
思想
把n个待排序的元素堪称为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含n-1个元素,排序过程中每次从无序表中取出第一个元素,把他的排序码依次与有序元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
代码
//插入排序
public static int[] insert(int[] arr) {
//使用for循环 简化代码
for (int i = 1; i < arr.length; i++) {
//定义待插入的数
int insertVal = arr[i];
int insertIndex = i - 1;//已经处于另一个数组的最后的下标
//给insertVal找到插入的位置
//说明:1.insertIndex>=0保证插入数组的位置不越界
//2.insertVal<arr[insertIndex] 待插入的值,还没有找到要插入的位置
//3.需要将arr[insertIndex]后移
while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
arr[insertIndex + 1] = arr[insertIndex];
insertIndex--;
}
//当退出while循环时,说明插入的位置已经找到,insertIndex
//或者为: insertVal>arr[insertIndex];
//判断是否要插入
if (insertIndex + 1 != i) {
arr[insertIndex + 1] = insertVal;
}
System.out.println("第" + i + "轮插入");
System.out.println(Arrays.toString(arr));
}
return null;
}
希尔排序
思想
插入排序的改进,把记录按下标一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减小,每组包含的关键词越来越多,当增量减为1,整个文件恰好被分为一组,算法便终止。
代码
下面写了两种方法 我们通常选择第二种,其效率更高,也更好理解。
//希尔排序 交换法
public static void shell1(int[] arr) {
int temp = 0;
//使用循环处理 分组数
// 3, 9, -1, 10, 11,8,-2,0
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
//遍历各组中所有的元素,共gap组,每组有arr.length/gap个元素,步长为gap
for (int j = i - gap; j >= 0; j -= gap) {
//如果当前元素大于加上步长后的那个元素。说明交换
if (arr[j] > arr[j + gap]) {
temp = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = temp;
}
}
}
}
System.out.println(Arrays.toString(arr));
}
// 优化 移位法
public static void shell2(int[] arr) {
//增量gap,并逐步的缩小增量
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
//从第gap个元素,逐个对其所在的组进行插入排序
for (int i = gap; i < arr.length; i++) {
int j=i;
int temp=arr[j];
if(arr[j]<arr[j-gap]){
while (j-gap>=0&&temp<arr[j-gap]){
//移动
arr[j]=arr[j-gap];
j-=gap;
}
//当退出while后,就给temp找到插入的位置
arr[j]=temp;
}
}
}
System.out.println(Arrays.toString(arr));
}
快速排序
思想
对冒泡排序的一种改进。
基本思想:通过一趟比较将要排列的数据分割成独立的两部分,其中一部分的所有数据都比另一部分的所有数据都要小,然后再按照此方法对这两部分数据分别进行快速排序,整个排序过程可以递归实现,依次达到整个数据都变为有序序列。
代码
1.递归
/**
* 递归
* */
public static void quick(int[] arr,int left,int right) {
int m = quickSort(arr, left, right);
// System.out.println(m);
if (left < right) {
if(m!=left) {
quick(arr, left, m - 1);
}
if (m != right) {
quick(arr, m + 1, right);
}
}
//System.out.println(Arrays.toString(arr));
}
public static int quickSort(int[] arr, int left, int right) {
int pivot = arr[left];
while (left < right) {
while (left < right && arr[right] >= pivot) {
right--;
}
arr[left] = arr[right];
while (left < right && arr[left] <= pivot) {
left++;
}
arr[right] = arr[left];
}
arr[left] = pivot;
return left;
}
2.非递归
//非递归
public void sort(int []arr, int left, int right) {
int privot, top, last;
Stack<Integer> s = new Stack<Integer>();
last=0;
top=0;
privot=QuickSort(arr, left, right);
if(privot>left+1) {
s.push(left);
s.push(privot-1);
}
if(privot<right-1) {
s.push(privot+1);
s.push(right);
}
while(!s.empty()) {
top = s.pop();
last = s.pop();
privot = QuickSort(arr, last, top);
if(privot>last+1) {
s.push(last);
s.push(privot-1);
}
if(privot<top-1) {
//System.out.println(top);
s.push(privot+1);
s.push(top);
}
}
}
public int QuickSort(int []arr, int left, int right) {
int pivot = arr[left];
while (left < right) {
while (left < right && arr[right] >= pivot) {
right--;
}
arr[left] = arr[right];
while (left < right && arr[left] <= pivot) {
left++;
}
arr[right] = arr[left];
}
arr[left] = pivot;
return left;
}
归并排序
思想
是利用归并的思想实现的排序方法,该算法采用经典的分治策略(分治法将问题分为一些小的问题然后递归求解,而治的阶段则将分的阶段得到的阶段得到的各答案“修补”在一起,即分而治之)
代码
//归并排序
public static void merge(int[] arr,int left,int mid,int right) {
int[] temp = new int[arr.length];
int i = left, j = mid + 1;
int k = 0;//表示temp的下标
while(i<=mid&&j<=right){
if(arr[i]<=arr[j]){
temp[k++]=arr[i++];
}
else {
temp[k++]=arr[j++];
}
}
//此时mid后面的已经合并完,剩下前面的全部合并
while (i <= mid) {
temp[k++] = arr[i++];
}//前面的合并完 剩下后面的直接写入temp中
while (j <= right) {
temp[k++] = arr[j++];
}
//完成后 将temp的值 赋值给原arr数组
for (int x = left,y=0; x <= right; x++,y++) {
arr[x] = temp[y];
}
}
public static void mergeSort(int[] arr,int start,int end){
if(start<end){//至少要大于1个元素 小于一个元素 则停止
int mid=(start+end)/2;
mergeSort(arr,start,mid);
mergeSort(arr,mid+1,end);
merge(arr,start,mid,end);
}
}
基数排序
思想
1>基数排序属于"分配式排序",又称"桶子法",顾名思义,他是通过键值的哥哥位的值, 将要排序的元素分配至某一些桶中,达到排序的作用
2>基数排序法是属于稳定性的排序,基数排序法的效率高的稳定性排序法
3>基数排序法是桶排序的扩展
4>基数排序是1887年赫尔曼.何乐礼发明的。他是这样实现的:将整数按位切割成不同的数字,然后按照每个位数分别比较。
代码(包含对负数的排序)
//基数排序 思路 可以按照个位十位 逐步放 最终寻找其共同处
public static void RadixSort(int[]arr){
int max=arr[0];
int[] brr=Arrays.copyOf(arr,arr.length);
for (int i = 0; i < brr.length; i++) {
if(brr[i]<0){
brr[i]/=-1;
}
}
for (int i = 1; i < brr.length; i++) {
if(max<brr[i]){
max=brr[i];
}
}
//求max是几位数: max+" "length
int maxLength=(max+"").length();
// 共有19个桶 前9个桶存放负数后10个桶存放0和正数
int[][] temp=new int[19][arr.length];
//记录每个桶中实际存放的多少个数据
int[] bucketElementCounts=new int[19];
for(int i=0,n=1;i<maxLength;i++,n*=10) {
for (int j = 0; j < arr.length; j++) {
//取出每个桶对应的位数的值
int m = arr[j]/n % 10;
//向前移动9位 确保负数在前九个 正数在后面
m+=9;
temp[m][bucketElementCounts[m]] = arr[j];
bucketElementCounts[m]++;
}
//按照每个桶的顺序 (一维数组的下标依次取出数据,放入原来的数组)
int index = 0;
//遍历每一桶,并将桶中的数据放入元素组
for (int k = 0; k < bucketElementCounts.length; k++) {
//如果桶中有数据 采访到原数组
if (bucketElementCounts[k] != 0) {
if(k<9){
//循环该桶放入 从后往前输入 负数 大的在后 小的在前
for (int l = bucketElementCounts[k]-1; l >=0; l--) {
//取出元素到arr
arr[index++] = temp[k][l];
}
}
else {
//循环该桶放入 从前往后
for (int l = 0; l < bucketElementCounts[k]; l++) {
//取出元素到arr
arr[index++] = temp[k][l];
}
}
}
bucketElementCounts[k]=0;
}
}
}
堆排序
思想
1>将待排序的序列构成一个大根堆
2>此时,整个序列的最大值就是堆顶的根节点
3>将其与末尾元素进行交换,此时末尾元素就是最大值
4>然后将剩余n-1个元素造成一个堆,这样会得到n个元素的次小值。如此反复执行,便得到 一个有序序列。
可以看到在构建大顶堆的过程中,元素的个数逐渐减少,最后就得到了一个有序序列。
代码
/**
* 功能:完成将以i对应的非叶子结点的树调整成大顶堆
* * 举例:int[]arr={4,6,8,5,9}=>i=1=>adjustHeap=>得到{4,9,8,5,6}
* * 如果我们再次调用adjustHeap 传入的是 i=0=>得到{4,9,8,5,6}
* * 得到=》{9,6,8,5,4}
* @param arr 待调整的数组
* @param i 表示非叶子节点在数组中索引
* @param length 表示对多少元素继续调整,length逐渐在减少
*/
public static void adjustHeap(int[] arr,int i,int length ){
int temp=arr[i];//先取出当前元素的值,保存在临时变量中
//开始调整 说明
//1.k=i*2+1 k表示i节点的左子节点
for(int k=i*2+1;k<length;k=k*2+1){
if(k+1<length&&arr[k]<arr[k+1]){
k++;
}
if(arr[k]>temp){
arr[i]=arr[k];
i=k;
}
else {
break;
}
}
//当for循环结束后,我们已经以i为父节点的树的最大值,放在了最顶(局部)
arr[i]=temp;
}
public static void heapSort(int[] arr){
for (int i = arr.length/2-1; i >= 0; i--) {
adjustHeap(arr,i,arr.length);
}
/**
* 2. 将堆顶元素与末尾元素交换,将最大的元素沉到数组末尾
* 3.重新调整结构,使其满足堆定义,然后交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,知道整个序列有序
*/
int temp=0;
for (int j = arr.length-1; j >0; j--) {
//交换
temp=arr[j];
arr[j]=arr[0];
arr[0]=temp;
adjustHeap(arr,0,j);
}
}
总结(时间复杂度比较)