排序算法
冒泡排序
1.比较相邻两个元素,如果第一个比第二个大,就交换他们两个;
2.对每一对相邻元素作同样的工作,从开始第一对到结尾最后一对,这样在最后的元素就会是最大的数;
3.重复以上步骤,直到排序完成.
import java.util.Arrays;
public class bubbleSort {
public static void main(String[] args) {
Integer[] arr=new Integer[]{4,2,7,1,8,23,4,15,62,1};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static <T extends Comparable<T>>void sort(T [] arr){
if (arr.length==0){
return;
}
for (int i=0;i<arr.length;i++){
boolean flag=true;//标记是否进行了交换,未交换为true
for (int j=0;j<arr.length-i-1;j++){
if (arr[j].compareTo(arr[j+1])>0){
//比较当前下标元素与下一下标元素大小,弱小于下一下标元素则交换
swap(arr,j,j+1);
flag=false;//发生了交换,标识变为false
}
}
if(flag){//flag为true时没有发生交换,判断为排序结束
break;
}
}
}
public static <T>void swap(T [] arr,int index1,int index2){
T temp=arr[index1];
arr[index1]=arr[index2];
arr[index2]=temp;
}
}
冒泡排序的特性
平均时间复杂度:O(n^2)
平均空间复杂度:O(1)
稳定性:稳定
基数排序
适用于给数值型数据进行排序。
1.从低位/高位开始将待排序的数按照这一位的值放到相应的编号为0~9的桶中;
2.等到低位排完得到一个子序列,再将这个序列按照次低位的大小进入相应的桶中;
3.一直排到最高位为止,数组排序完成。
个位数如下:
十位数如下:
import java.util.Arrays;
import java.util.Vector;
public class LSDSort {
public static void main(String[] args) {
int[] arr={14, 21, 12, 31, 4, 12, 4, 57};
LSDSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void LSDSort(int a[]) {
int max = a[0];
for (int i = 1; i < a.length; i++) {
if (a[i] > max)
max = a[i];
}
int len = 0; // 存贮最大的数的位数,用来判断需要进行几轮基数排序
while (max > 0) {
max = max / 10;
len++;
}
for (int pos = 0; pos < len; pos++) // 按位数运行几次,每次都分裂成10份,在顺序链接
{
// 以下内容应为每次运行时,分割成0-9 个桶,然后顺序链接
@SuppressWarnings("unchecked")
Vector<Integer> num[] = new Vector[10];
for (int i = 0; i < num.length; i++) {
num[i] = new Vector<Integer>();
}
int gap = 1; // 用来取出当前的对应的位数的数
for (int i = 0; i < pos; i++) {
gap = gap * 10;
}
for (int i = 0; i < a.length; i++) // 对每一个数进行判断位数
{
int x = 0; // 用来表示当前的位数上的数的大小
x = a[i] / gap;
x = x % 10;
num[x].add((Integer)a[i]);
}
// 将排序的结果顺序连接起来
int count = 0;
for (int i = 0; i < num.length; i++) {
for (int j = 0; j < num[i].size(); j++) {
a[count++] = (int)num[i].get(j);
}
}
}
}
}
基数排序的特性
平均时间复杂度:O(d(n+r))
r表示基数,d表示位数
平均空间复杂度:O(n+r)
稳定性:稳定
选择排序
1.首先在未排序的序列中找到最小/最大的元素,存放在排序序列的起始/末尾位置;
2.然后再从剩余的元素中继续寻找最小/最大的元素,和已排序好的队列末尾/队头进行交换,
3.循环以上步骤,直到所有的元素都排序完毕。
import java.util.Arrays;
public class selectSort {
public static void main(String[] args) {
Integer [] arr={5,4,12,75,6,1,5,69,42,3};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static <T extends Comparable<T>> void sort(T [] arr){
if (arr.length==0) return;
int minIndex=0;//标记最小值的下标
for (int i=0;i<arr.length;i++){
minIndex=i;
for(int j=i+1;j<arr.length;j++){
if (arr[minIndex].compareTo(arr[j])>0){
//获取本次循环最小值的下标
minIndex=j;
}
}
swap(arr,minIndex,i);//将当前最小值放在拍好序的队列后
}
}
public static <T> void swap(T [] arr,int index1,int index2){
T temp=arr[index1];
arr[index1]=arr[index2];
arr[index2]=temp;
}
}
选择排序的特性
平均时间复杂度:O(n^2)
平均空间复杂度:O(1)
稳定性:不稳定
插入排序
import java.util.Arrays;
public class insertSort {
public static void main(String args[])
{
Integer arr[] = {12, 11, 5, 13, 34, 5, 6, 56, 14};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static<T extends Comparable<T>> void sort(T arr[])
{
for (int i=1; i<arr.length; i++)
{
T temp = arr[i];
int j = i-1;
while (j>=0 && arr[j].compareTo(temp)>0)
//拿获取到的当前下标的值去拍好序的队列里找到对应的位置,进行插入
{
arr[j+1] = arr[j];//第一次循环j+1即i对应位置
j = j-1;
}
arr[j+1] = temp;
}
}
}
*
图片来源于GeeksForGeeks
插入算法的特性
平均时间复杂度:O(n^2)
平均空间复杂度:O(1)
稳定性: 稳定
折半插入排序
折半插入排序即在将数据插入排序好的数据队列中时,采用二分的方式查找对应位置,这里不做代码展示。
希尔排序
选择增量(数组长度/2)进行分组;缩小增量继续以增量/2 的方式进行分组。{n/2,(n/2)/2,(n/2)/4,…,1}增量序列。
1.选择一个增量序列,按照增量序列个数 m,进行 m 趟排序。
2.每趟排序根据对应的增量次数分别进行元素的分组操作,对组内进行直接插入排序操作。
3.继续下一个增量,分别进行分组直接插入操作。
4.重复第三个步骤,直到增量变成 1,所有元素在一个分组内,希尔排序结束。
import java.util.Arrays;
public class shellSort {
public static void main(String[] args) {
Integer[] arr = {12, 4, 65, 13, 7, 4, 15, 16, 23};
shell(arr.length, arr);
System.out.println(Arrays.toString(arr));
}
public static <T extends Comparable<T>> void shell(int partition, T[] arr) {
if (arr.length==0) return;
for (int i = partition / 2; i > 0; i /= 2) {
sort(arr, i);
}
}
public static <T extends Comparable<T>> void sort(T[] arr, int gap) {
int i = gap, j = 0;//j表示分组下的前一个下标,i表示下一个下标
for (;i < arr.length; i++) {
T temp = arr[i];//记录当前i下标的元素,方便交换
for (j = i - gap; j >= 0; j -= gap) {
if (temp.compareTo(arr[j]) < 0) {//当i下标元素比j下标元素小时,进行交换(升序)
arr[j + gap] = arr[j];//j+gap表示该分组下当前j下标的下一位元素
} else {
break;//如果满足条件,说明该组元素已排好序了,跳出循环
}
}
arr[j + gap] = temp;//最后一次循环后j-gap,当前j+gap为最后一次循环时的j
}
}
}
希尔排序的特性
时间复杂度:O(n^1.3~2)
平均空间复杂度: O(1)
稳定性分析: 不稳定
归并排序
1.开始以间隔为 1 的进行归并,即第一个元素跟第二个进行归并,第三个与第四个进行归并;
2. 然后,再以间隔为 2 的进行归并,1-4 进行归并,5-8 进行归并;
3. 再以 22 的间隔,同理,直到 2k 超过数组长度为止。
4. 当不够两组进行归并时,如果超过 k 个元素,仍然进行归并;如果剩余元素不超过 k 个元素,那么直接复制给中间数组。
import java.util.Arrays;
public class mergeSort {
public static void main(String[] args) {
Integer [] arr={12,4,2,15,35,37,4,7,12,23,42,1,62,1,3};
merge(arr);
System.out.println(Arrays.toString(arr));
}
public static <T extends Comparable<T>> void merge(T [] arr){
if (arr.length==0) return;
for(int i=1;i<arr.length;i*=2){//从1开始分归并段,再逐次乘2作为归并段的跨度
sort(arr,i);
}
}
public static <T extends Comparable<T>> void sort(T [] arr,int len){
//找出前后两个归并段的开头和末尾,进行两两归并排序
int low1=0,high1=low1+len-1;
int low2=high1+1;
int high2=low2+len-1>arr.length-1?arr.length-1:low2+len-1;
T[] brr=(T[])new Comparable[arr.length]; //创建中间数组
int i=0;
while(low2<arr.length){//确保有两个归并段
while(low1<=high1&&low2<=high2){//确保归并段有数据
if(arr[low1].compareTo(arr[low2])>0){//谁小先放谁
brr[i++]=arr[low2++];
}else{//相等的情况先放前面的数据,确保稳定性
brr[i++]=arr[low1++];
}
}
while(low1<=high1){//将第一段剩余数据直接存放
brr[i++]=arr[low1++];
}
while(low2<=high2){//将第二段剩余数据直接存放
brr[i++]=arr[low2++];
}
//将归并段下标后移,开始新的相邻归并段排序
low1=high2+1;
high1=low1+len-1;
low2=high1+1;
high2=low2+len-1>arr.length-1?arr.length-1:low2+len-1;
}
while(low1<arr.length){//如果不满足两个归并段,直接拷贝数据
brr[i++]=arr[low1++];
}
for(i=0;i<arr.length;i++){//将排好序的数据替换原有数据
arr[i]=brr[i];
}
brr=null;//将中间数组置空
}
}
归并排序的特性
平均时间复杂度:O(nlogn)
平均空间复杂度:O(n)
稳定性:稳定
堆排序
1.先将初始数组建成一个大根堆,此堆为初始的无序;
2.再将关键字最大的记录 R1和无序区的最后一个记录 R[n]交换,由此得到新的无序区 R[1…n-1]和有 序区 R[n]。由于交换后新的根 R[1]可能违反堆性质,故应将当前无序区 R[1…n-1]调整为堆。然后再次将 R[1…n-1]中关键字最大的记录 R[1]和该区间的最后一个记录 R[n-1]交换,由此得到新的无序区 R[1…n-2]和有 序区 R[n-1…n],同样要将 R[1…n-2]调整为堆。
3.直到无序区只有一个元素为止。
import java.util.Arrays;
public class heapSort {
public static void main(String[] args) {
Integer [] arr={12,4,2,15,35,37,4,7,12};
heapSort(arr);
System.out.println(Arrays.toString(arr));
}
public static <T extends Comparable<T>>void heapSort(T[] arr){
int i=0;
int len=arr.length-1;
for(i=(len-1)/2;i>=0;i--){//i 代表要调整根节点的下标
adjust(arr, i, len);//最大堆建立好了
}
int temp=0;
for(i=0;i<arr.length;i++){
//0 下标 根保存最大值
// arr[0]和堆中相对的"最后"元素进行交换
swap(arr,0,len-i);
adjust(arr, 0, len-i-1);
}
}
private static <T>void swap(T[] arr,int index1,int index2){
T temp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = temp;
}
private static <T extends Comparable<T>>void adjust(T[] arr,int start,int end){
T temp=arr[start];
for(int i=2*start+1;i<=end;i=2*i+1){
if(i+1<=end && arr[i].compareTo(arr[i+1]) < 0){
i++;//i 指向两个子节点中较大的值
}
if(temp.compareTo(arr[i]) < 0){
arr[start]=arr[i];
start=i;
}
else{
break;
}
}
arr[start]=temp;
}
}
堆排序的特性
平均时间复杂度:O(nlogn)
平均空间复杂度:O(1)
稳定性:不稳定
快速排序
1.选择基准:在待排序列中,按照某种方式挑出一个元素,作为 “基准”(pivot) ;
2.分割操作:以该基准在序列中的实际位置,把序列分成两个子序列。此时,在基准左边的元素都比该基准小,在基准右边 的元素都比基准大 ;
3.递归地对两个序列进行快速排序,直到序列为空或者只有一个元素。
public class quickSort{
public static void main(String[] args) {
Integer[]arr=new Integer[]{1,2,12,57,1,3,35,4,21};
sort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static <T extends Comparable<T>> void sort(T[] arr,int left,int right){
int part=swap(arr,left,right);
//分治策略解题,基准左侧,右侧分别进行快速排序
if(left+1<part)
sort(arr,left,part-1);
if(part+1<right)
sort(arr,part+1,right);
}
public static <T extends Comparable<T>> int swap(T[] arr,int left,int right) {
T temp = arr[left];
while (left < right) {
while (left < right && arr[right].compareTo(temp) > 0)//找到比基准小的元素下标
right--;
if (left == right)//遍历完了,跳出循环
break;
else{//否则进行交换
arr[left]=arr[right];
}
while(left<right&&arr[left].compareTo(temp)<=0){//找到比基准大的下标
left++;
}
if(left==right)
break;
else{
arr[right]=arr[left];
}
}
arr[left]=temp;
return left;
}
}
快速排序的特性
平均时间复杂度:O(nlogn)
平均空间复杂度:O(1)
稳定性:不稳定
快速排序的优化
基准的选择
方法一:固定位置
选择第一位或者最后一位作为基准
方法二:随机选取基准
方法三:三数取中
一般的做法是使用,左端,右端,中心的三个数,取其中的中值作为基准。