一、冒泡排序算法
1、冒泡排序思想:
对要排序的序列从前到后依次比较相邻的元素的值,若发现逆序则交换,使值较大的元素从前往后移。
2、冒泡排序图解:
- 总共
arr.length-1
次大的循环 - 每一次大循环代表一趟排序,每次排序比较相邻两个两个元素的值,比较后两个元素都后移(这里想象成两个索引都后移)
3、冒泡排序算法:
public static void bubbleSort(int[] arr){
//第一层循环代表总共进行多少趟排序
for(int i=0;i<arr.length-1;i++){
//第二层循环代表每趟进行多少次比较
for(int j=0;j<arr.length-1-i;j++){
if(arr[j]>arr[j+1]){
int temp;
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
public class _1_冒泡排序算法 {
public static void main(String[] args) {
int arr[] = {3, 9, -1, 10, 20};
System.out.println(Arrays.toString(bubbleSort((arr))));
}
}
4、冒泡排序算法优化:
如果某趟排序中没有发生一次交换,就提前结束冒泡排序
public static void bubbleSort(int[] arr){
//第一层循环代表总共进行多少趟排序
for(int i=0;i<arr.length-1;i++){
boolean flag = false;
//第二层循环代表每趟进行多少次比较
for(int j=0;j<arr.length-1-i;j++){
if(arr[j]>arr[j+1]){
flag = true;
int temp;
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
if(!flag){
break;
}
}
}
二、选择排序算法
1、选择排序思想:
1. 第1次从arr[0]~arr[n-1]中选取最小的值与arr[0]交换
2. 第2次从arr[1]~arr[n-1]中选取最小的值与arr[1]交换
3. 第3次从arr[2]~arr[n-1]中选取最小的值与arr[2]交换
以此类推。。。。。。。
4. 第n-1次从arr[n-2]~arr[n-1]中选取最小的值与arr[n-2]交换
2、选择排序图解:
具体步骤:
- 共有
arr.length-1
次排序 - 先假定当前这个数是最小数,然后和后面的每个数进行比较,如果发现后面的有比当前数更小,就重新确定最小数,并得到下标
- 遍历到数组最后时,就得到本轮最小数和下标
- 交换当前这个数与最小数
3、选择排序算法实现:
public static void selectSort(int[] arr){
for(int i=0;i<arr.length-1;i++) {
//假定当前数为最小数,记录最小数和对应索引
int minIndex = i;
int min = arr[i];
//将arr[i]与后面的数比较,如果后面的数更小重置min和对应下标
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;
}
}
}
测试代码:
public class _2_选择排序算法 {
public static void main(String[] args) {
int[] arr = {34,26,78,60,15};
System.out.println(Arrays.toString(selectSort(arr)));
}
}
三、插入排序算法
1、插入排序思想:
把n个待排序的元素看成一个有序表和一个无序表,开始时有序表只包含一个元素,无序表中含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码一次与有序元素的排序码比较,将他插入到有序表中的适当位置,使之成为新的有序表。
2、插入排序图解:
3、插入排序算法实现:
public static void insertSort(int[] arr){
for(int i=1;i<arr.length;i++){
int insertValue = arr[i];
int insertIndex =i-1;
while(insertIndex>=0 && insertValue<arr[insertIndex]){
arr[insertIndex+1] = arr[insertIndex];
insertIndex--;
}
if(insertIndex+1!=i){
arr[insertIndex+1] = insertValue;
}
}
}
建议Debug来理解。
四、希尔排序算法
1、希尔排序思想:
插入排序,当需要插入的数较小是,后移的次数就会增多,对效率有所影响。
希尔排序是一种改进的插入排序,它不需要执行太多的数据搬移操作。可以减少插入排序法中数据搬移的次数,以加速排序的进行。排序的原则是将数据区分成特定间隔的几个小区块,以插入排序法排完区块内的数据后,再逐渐减少间隔的距离。
2、希尔排序图解:
3、希尔排序算法(交换式)实现:
对有序序列插入时使用交换法(不推荐,和我们的希尔排序思想违背):
public static void shellSort(int[] arr){
for(int gap=arr.length/2;gap>0;gap/=2){
//在每一组里面进行排序(交换)
for(int i=gap;i<arr.length;i++){
for (int j=i-gap;j>=0;j-=gap){
if(arr[j]>arr[j+gap]){
int temp;
temp = arr[j];
arr[j] = arr[j+gap];
arr[j+gap] =temp;
}
}
}
}
}
测试:结果发现使用交换式,效率反而降低了,时间复杂度更高了
public class _4_希尔排序算法 {
public static void main(String[] args) {
int[] arr = {63,92,27,36,45,71,58,7};
System.out.println(Arrays.toString(shellSort(arr)));
}
}
4、希尔排序算法(移位式)实现:
对有序序列插入时使用移动法:对上面交换法的改进(在每一组内进行插入排序,正宗的希尔排序,即插入排序的高版本)
public static void shellSort2(int[] arr){
for(int gap=arr.length/2;gap>0;gap/=2){
//在每一组里面进行插入排序
for(int i=gap;i<arr.length;i++){
int insertIndex = i;
int insertValue = arr[insertIndex];//定义待插入的数,并保存
while(insertIndex-gap>=0 && insertValue<arr[insertIndex-gap]){
arr[insertIndex] = arr[insertIndex-gap];
insertIndex-=gap;
}
arr[insertIndex] = insertValue;
}
}
}
五、快速排序算法
1、快速排序算法思想:
先在数据中找到一个虚拟的中间值,并按此中间值将所有打算排序的数据分为两部分。其中,小于中间值的数据放在左边,大于中间值的数据放在右边,再以同样的方式分别处理左、右两边的数据,直到排序完为止。
快速排序的步骤:
(1) 选择基准值。
(2) 将数组分成两个子数组:小于基准值的元素和大于基准值的元素。
(3) 对这两个子数组进行快速排序。
2、快速排序图解:
3、快速排序算法实现:
public static void quickSort(int[] arr, int left, int right) {
int l = left; //左下标
int r = right; //右下标
int pivot = arr[(left + right) / 2];//pivot 中轴值
int temp = 0;
//while循环的目的是让比pivot 值小放到左边比pivot 值大放到右边
while (l < r) {
//在pivot的左边一直找,找到大于等于pivot值,才退出
while (arr[l] < pivot) {
l += 1;
}
//在pivot的右边一直找,找到小于等于pivot值,才退出
while (arr[r] > pivot) {
r -= 1;
}
//如果l >= r说明pivot 的左右两的值,已经按照左边全部是
//小于等于pivot值,右边全部是大于等于pivot值
if (l >= r) {
break;
}
//交换
temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
//如果交换完后,发现这个arr[l] == pivot值 相等 r--, 前移
if (arr[l] == pivot) {
r -= 1;
}
//如果交换完后,发现这个arr[r] == pivot值 相等 l++, 后移
if (arr[r] == pivot) {
l += 1;
}
}
// 如果 l == r, 必须l++, r--, 否则为出现栈溢出
if (l == r) {
l += 1;
r -= 1;
}
//向左递归
if (left < r) {
quickSort(arr, left, r);
}
//向右递归
if (right > l) {
quickSort(arr, l, right);
}
}
六、归并排序算法
1、算法思想:
2、算法实现:
//分+合方法
public static void mergeSort(int[] arr, int left, int right, int[] temp) {
if(left < right) {
int mid = (left + right) / 2;
mergeSort(arr, left, mid, temp);
mergeSort(arr, mid + 1, right, temp);
merge(arr, left, mid, right, temp);
}
}
//合并的方法
/**
*
* @param arr 排序的原始数组
* @param left 左边有序序列的初始索引
* @param mid 中间索引
* @param right 右边索引
* @param temp 做中转的数组
*/
public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left; // 初始化i, 左边有序序列的初始索引
int j = mid + 1; //初始化j, 右边有序序列的初始索引
int t = 0; // 指向temp数组的当前索引
//(一)
//先把左右两边(有序)的数据按照规则填充到temp数组
//直到左右两边的有序序列,有一边处理完毕为止
while (i <= mid && j <= right) {//继续
//如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
//即将左边的当前元素,填充到 temp数组
//然后 t++, i++
if(arr[i] <= arr[j]) {
temp[t] = arr[i];
t += 1;
i += 1;
} else { //反之,将右边有序序列的当前元素,填充到temp数组
temp[t] = arr[j];
t += 1;
j += 1;
}
}
//(二)
//把有剩余数据的一边的数据依次全部填充到temp
while( i <= mid) { //左边的有序序列还有剩余的元素,就全部填充到temp
temp[t] = arr[i];
t += 1;
i += 1;
}
while( j <= right) { //右边的有序序列还有剩余的元素,就全部填充到temp
temp[t] = arr[j];
t += 1;
j += 1;
}
//(三)
//将temp数组的元素拷贝到arr
//注意,并不是每次都拷贝所有
t = 0;
int tempLeft = left; //
//第一次合并 tempLeft = 0 , right = 1 // tempLeft = 2 right = 3 // tL=0 ri=3
//最后一次 tempLeft = 0 right = 7
while(tempLeft <= right) {
arr[tempLeft] = temp[t];
t += 1;
tempLeft += 1;
}
}
这个算法运用了递归调用,如果想真正的理解他,建议手动Debug几遍,一遍不懂就多几次。
七、基数排序算法
1、基数排序思想:
2、算法实现:
//基数排序方法
public static void radixSort(int[] arr) {
//1. 得到数组中最大的数
int max = arr[0];
for(int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
//得到最大数是几位数
int maxLength = (max + "").length();
//定义一个二维数组,表示10个桶, 每个桶就是一个一维数组
//1. 二维数组包含10个一维数组
//2. 为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.length
//3. 名明确,基数排序是使用空间换时间的经典算法
int[][] bucket = new int[10][arr.length];
//为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
//比如:bucketElementCounts[0] , 记录的就是 bucket[0] 桶的放入数据个数
int[] bucketElementCounts = new int[10];
for(int i = 0 , n = 1; i < maxLength; i++, n *= 10) {
//(针对每个元素的对应位进行排序处理), 第一次是个位,第二次是十位,第三次是百位..
for(int j = 0; j < arr.length; j++) {
//取出每个元素的对应位的值
int digitOfElement = arr[j] / n % 10;
//放入到对应的桶中
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
bucketElementCounts[digitOfElement]++;
}
//按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
int index = 0;
//遍历每一桶,并将桶中是数据,放入到原数组
for(int k = 0; k < bucket.length; k++) {
//如果桶中,有数据,我们才放入到原数组
if(bucketElementCounts[k] != 0) {
//循环该桶即第k个桶(即第k个一维数组), 放入
for(int l = 0; l < bucketElementCounts[k]; l++) {
//取出元素放入到arr
arr[index++] = bucket[k][l];
}
}
//第i+1轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!!
bucketElementCounts[k] = 0;
}
}
}
同样那句话,理解算法最快速的方法是在理解思路的基础上,多进行debug。