1、 冒泡排序及其优化
核心思想: 通过每一轮的比较,把较大的数字放在末尾,每一轮比较完之后,下一轮的比较的次数为该数组的长度减去循环次数再减一。
举例: 有以下数组 5 4 6 7 1 9 8
第一次循环:
第一趟比较:4 5 6 7 1 8 9
第二趟比较:4 5 6 1 7 8 9
第三趟比较:4 5 1 6 7 8 9
第四、五、六趟比较结果与第三趟一样
第二次循环:
第一趟比较:4 1 5 6 7 8 9
第二趟比较:1 4 5 6 7 8 9
…
举得例子比较特殊,两次循环就完成了排序。
代码如下:
public int[] BubbleSort(int arr)
{
int temp = 0;
for(int i = 0; i < arr.length; i++) //外层循环控制循环次数
{
for(int j = 0; j < arr.length-i-1; j++) //内层循环控制比较次数
{
if(arr[j]>arr[j+1] )
{
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
return arr;
}
现在来思考这样一个问题,看一下数字:1,2,3,4,5,6,7,8,10,9;这段数字我们只需循环一次即可获得正确的排序,但是依据上面的代码,还是需要比较arr.length-1次,因此浪费时间,需要对其进行优化。我们可以在循环体内添加一个标记,来判断是否进行了比较,如果为进行比较则说明该数组本身就有序。
public int[] BubbleSor(int[] arr)
{
for(int i = 0; i < arr.length;i++)
{
int flag = 0; // 设置标记
for(int j = 0; j < arr.length - i -1; j++)
{
if(arr[j] > arr[j+1])
{
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] == temp;
flag = 1;
}
}
if(flag == 0)
{
return arr;
}
}
return arr;
}
冒泡排序优化2:优化一仅仅适用于连片有序而整体无序的数据(例如:1, 2,3 ,4 ,7,6,5)。但是对于前面大部分是无序而后边小半部分有序的数据(1,2,5,7,4,3,6,8,9,10)排序效率也不可观,对于种类型数据,我们可以继续优化。既我们可以记下最后一次交换的位置,后边没有交换,必然是有序的,然后下一次排序从第一个比较到上次记录的位置结束即可。
public int[] BubbleSort(int[] arr)
{
int temp = 0;
int flag = 0;
int k = arr.length-1;
int pos = 0;
for(int i = 0; i< arr.length;i++)
{
flag = 0;
for(int j = 0; j < k; j++)
{
if(arr[j] > arr[j+1])
{
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
flag =1;
pos = j;
}
}
if(flag == 0)
{
return arr;
}
k = pos;
}
return arr;
}
冒泡排序优化3:一次排序可以确定两个值,正向扫描找到最大值交换到最后,反向扫描找到最小值交换到最前面。例如:排序数据1,2,3,4,5,6,0
public int[] BubbleSort(int[] arr)
{
int pos =0;//存储最后一个交换位置
int flag = 0; //存储是否交换过
int temp = 0;//临时变量
int n=0;
int k = arr.length -1;
for(int i = 0; i < arr.length; i++)
{
flag = 0;
for(int j = n; j < k; j++)
{
if(arr[j] > arr[j+1])
{
temp= arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
pos = j;//获取最后交换位置
flag = 1;
}
}
if(flag == 0)
{
return arr;
}
k = pos;//下一次比较到记录位置即可
//反向查找
for(int j = k; j>n;j--)
{
if(arr[j] < arr[j-1])
{
temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;
flag = 1;
}
}
n++;
if(flag == 0)
{
return arr;
}
}
return arr;
}
2、直接插入排序
核心思想:把数组分成两个序列,一个有序序列和一个无序序列,将无序序列中的数字依次插入有序序列中,并保证有序序列中的数字任然有序。
public int[] sort(int[] arr)
{
int j = 0;
for(int i = 1; i < arr.length; i++)
{
int temp = arr[i];
j =i-1;
while(j >=0&&temp<arr[j])
{
arr[j+1] = arr[j];
j--;
}
arr[j+1] = temp;
}
return arr;
}
3、希尔排序
核心思想:希尔排序是将待排序的数组元素 按下标的一定增量分组 ,分成多个子序列,然后对各个子序列进行直接插入排序算法排序;然后依次缩减增量再进行排序,直到增量为1时,进行最后一次直接插入排序,排序结束。
有以下数组:5 6 4 1 12 10 3 7
**第一次:**增量为 gap = arr.length / 2,则将数组分成一下4组
第一次分组排完序之后结果为:5 6 3 1 12 10 4 7
第二次 : gap = gap / 2;
第二次分完组之后结果为:3,1,5,6,4,7,12,10
第三次 gap = gap / 2=1
对以上数组进行一次插入排序即可。
有了以上思路即可以写代码了。
public void swap(int[] arr,int first, int last)
{
int temp = arr[first];
arr[first] = arr[last];
arr[last] = temp;
}
public int[] shellSort(int[] arr)
{
for(int gap = arr.length / 2; gap > 0; gap/=2)
{
for(int i = gap;i < arr.length;i++)
{
int j = i;
while(j-gap>=0&&arr[j]<arr[j-gap])
{
swap(arr, j,j-gap);
j -= gap;
}
}
}
return arr;
}
4、堆排序
什么是堆???
可以把堆看成是一颗完全二叉树,并且这颗完全二叉树有一个特点:根节点比其左右叶子结点的值都大或者都小。如果每一个根节点比其左右叶子节点的值都大,则称之为大顶堆,反之就称为小顶堆。这一规则必须在完全二叉树的任一子树都满足。
完全二叉树的特点:设根节点的索引值为index;
(1)左节点的索引值为 2indexe +1;
(2)右节点的索引值为 2index+2;
(3)其父节点的值为(index-1)/2;
//构造小顶堆
public static void minHeap(int[] data,int size,int index)
{
//左子节点
int leftNode = 2*index+1;
//右子节点
int rightNode = 2*index+2;
int max = index;
//和两个子节点分别对比,找出最大的节点
if(leftNode < size&&data[leftNode] <data[max])
{
max = leftNode;
}
if(rightNode< size&&data[rightNode] < data[max])
{
max = rightNode;
}
//交换位置
if(max!=index)
{
int temp = data[index];
data[index] = data[max];
data[max] = temp;
//递归进行该节点子树小顶堆的创建
minHeap(data,size,max);
}
}
//构造完全的小顶堆
public static void BuildminHeap(int[] data)
{
int start = (data.length-1)/2;
for(int i = start; i >=0;i--)
{
minHeap(data,data.length,i);
}
}
//进行小顶堆排序
public static void minHeapSort(int[] data)
{
for(int i = data.length-1; i>0;i--)
{
int temp = data[0];
data[0] = data[i];
data[i] = temp;
minHeap(data, i, 0);
}
}
5、快速排序
(1)挖坑法
public int partition(int[] arr, int left, int right)
{
if(left<right)
{
int temp = arr[left];
while(left<right)
{
//如果右边的数大于基数temp,则向左移动
while(left<right&&arr[right] >= temp)
{
right--;
}
//如果不大于,则把右边的数赋值给左边
arr[left] = arr[right];
//如果左边的数小于基数,则向右移动
while(left<right&&arr[left] <= temp)
{
left++;
}
//如果左边的数大于基数,则左边的数赋值给右边
arr[right] = arr[left];
}
arr[left]=temp;
}
return left;
}
public int[] QSort(int[] arr, int left, int right)
{
int[] newArr = arr;
if(left < right)
{
int pivot = partition(newArr, left, right);
QSort(newArr,left,pivot-1);
QSort(newArr, pivot+1,right);
}
return newArr;
}
###(2)快速排序的优化
三数取中法
public void MedianOfThree(int[] arr, int left, int right)
{
int mid = left+(left+right);
if(arr[left] > arr[right])//把最大值放在数组的末尾
{
swap(arr, left, right);
}
if(arr[mid]> arr[right])//把最大值放在数组的末尾
{
swap(arr, mid, right);
}
if(arr[mid]>arr[left])//把最小值放在中间
{
swap(arr, mid, left);
}
}
//交换
public void swap(int[] arr, int left, int right)
{
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
public int[] QSort_MedianOfThree(int[] arr, int left, int right)
{
//[1]先进行数字调换
MedianOfThree(arr,left,right);//将中间数放到开头
int[] newArr = arr;
if(left < right)
{
int pivot = partition(newArr, left,right);
QSort(newArr,left,pivot-1);
QSort(newArr, pivot+1,right);
}
return newArr;
}
三数取中+插入排序
public int[] insertSort(int[] arr)
{
int j = 0;
for(int i = 1; i< arr.length; i++)
{
int temp = arr[i];
for( j = i-1; j >=0&&arr[j]>temp;j--)
{
arr[j+1] = arr[j];
}
arr[j+1] = temp;
}
return arr;
}
public int[] QSort_Insert(int[] arr, int left, int right)
{
//如果数组的长度小于10,则采用插入排序
if(right - left +1 < 10)
{
return insertSort(arr);
}
//[1]先进行数字调换
MedianOfThree(arr,left,right);//将中间数放到开头
int[] newArr = arr;
if(left < right)
{
int pivot = partition(newArr, left,right);
QSort(newArr,left,pivot-1);
QSort(newArr, pivot+1,right);
}
return newArr;
}
6、选择排序
核心思想:每次从数组中寻找最小的数字,放在数组的开头。思想比较简单,这里就不做过多的演示。
public int[] selectSort(int[] arr)
{
for(int i = 0;i < arr.length;i++)
{
int minIndex = i;
//每次搜寻第i+1之后的元素
for(int j = i+1;j < arr.length;j++)
{
if(arr[j] < arr[minIndex])
{
minIndex=j;
}
}
if(i!=minIndex)
{
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
return arr;
}
7、基数排序
适合在待排序的数组中,个位、10位、百位…都有,数字差别较大。
基本思想: 将整数按位数切割成不同的数字,然后按每个位数分别比较。
**具体做法是:**将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
代码如下:
可以把代码中的二维数组想象成一个表格,便于理解。
public int[] radixSort(int[]A, int n)
{
int max = A[0];
for(int i = 1; i< n; i++)
{
if(max < A[i])
{
max = A[i];
}
}
double d = Math.pow(10, String.valueOf(max).length());
int k = 1;
int[][] t=new int[10][n];
int[] num = new int[n];//记录每个桶中存入数的个数
while(k < d)
{
for(int a :A)
{
int m = (a/k)%10;
t[m][num[m]]=a;//把数字放入桶中
num[m]++;
}
int c=0;
for(int i =0; i<n;i++)
{
if(num[i] !=0)
{
for(int j=0; j < num[i];j++)
{
A[c++] = t[i][j];
}
}
num[i] = 0;
}
k*=10;
}
return A;
}
8、归并排序
public int[] sort(int[] arr, int low, int high)
{
int[] newArr = arr;
int middle = (low+high)/2;
if(low < high)
{
sort(newArr, low, middle);
sort(newArr, middle+1,high);
merge(newArr, low, middle, high);
}
return newArr;
}
public int[] merge(int[] arr, int low, int middle,int high)
{
//用域存储归并后的临时数组
int[] temp = new int[high - low +1];
//记录第一个数组中需要遍历的下标
int i= low;
//记录第二个数组中需要遍历的下标
int j = middle+1 ;
//用于记录在临时数组中存放的下标
int index = 0;
//遍历两个数组,取出小的数字放入临时数组中
while(i<=middle&&j<=high)
{
//把小的数据放入临时数组中
if(arr[i] < arr[j])
{
temp[index] = arr[i];
i++;
}
else
{
temp[index] = arr[j];
j++;
}
index++;
}
//处理多余数据
while(j<=high)
{
temp[index]=arr[j];
j++;
index++;
}
while(i<=middle)
{
temp[index]=arr[i];
i++;
index++;
}
for(int k = 0; k < temp.length;k++)
{
arr[k+low] = temp[k];
}
return arr;
}