Java算法入门:位运算与三种排序算法

177 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情


算法

什么是算法?对一个问题做出的流程设计。

算法的分类:大体上分为两类:①明确知道怎么算的流程②明确知道怎么尝试的流程。

图灵之前的计算机都只能去进行计算,而不知道怎么尝试。图灵把“怎么试”变成了理论。


数据结构

数据结构包含类似数组那样的连续结构,还有像链表,二叉树那样的跳转结构。


位运算

我们声明一个int变量,我们看上去是一个十进制数。但他的底层是32位的二进制数。long类型为64位。而且在Java中int是有符号的。

比如我现在要打印出来数的32位是什么

👇打印底层二进制的一个算法

public static void print(int num) {
        for(int i = 31;i >=0 ;i--) {
                System.out.print((num & (1 << i)) == 0? "0" : "1");
        }
        System.out.println();
}

下面介绍一下这个算法在干嘛

刚才说了,一个int数底层是32位的,即下标从0-31。虽然我们平时打印出来的还是十进制,其实底层做了十进制转换。

既然底层是32位,那么代码中“&”运算符后面的1也是32位,即31个0最后一个是1这样的32位二进制数。

1 << i 的意思是向左移动 i 位。比如刚进去的时候 i 是31,那么这个1在底层二进制中就要向左移动31位,移动之前1呆的位置由0来补。移动31位之后就成了1后面跟了31个0

然后就是&运算符,这个运算符是只有1和1进行&得出的结果才能是1,其他都是0 image.png

结论:一个数左移一位相当于×2。即num<<1 == num*2;


反码

Q:一个int型能表示多大的范围?

A:负2的31次方 ~ 2的31次方-1

底层32位,第一位是2的0次方,一直到2的31次方。所以按理说一个int型是可以表示从0~2的32次方-1这么多数(即从2的0次方加到2的31次方),大约42亿多。但实际我们发现,Java中整型最大值(Integer.MAX_VALUE)却是21亿多。

通过打印底层32位二进制我们会看到,32位的信息中除了第一位是0其他全部是1,也就是在系统中他的32位不是都使用了。留着最高位有一个特殊含义。而真正表示值的位置其实是从0~30位。所以他表示最高是2的31次方-1这么个数

Q:这个特殊含义是什么?

int整型既可以表示正数,也可以表示负数,在Java中没有无符号整型,像C++中的无符号整型就可以表示到2的32次方-1

所以最高位我们拿来做符号位,如果此位为0,那么就是正数。如果此位为1,那么就是负数

Q:为什么负数可以到2的31次方,正数只能到2的31次方-1

A:因为把0算到正数了,0是2的0次方。算上0就是2的31次方了


如果给你一个符号位为1的32位二进制数(即负数),那么如何计算他的值呢?

很简单,除了符号位,剩下的位置状态取反再+1

比如你去打印十进制的-1的底层32位二进制,你会发现都是1。

实际上运算中的加减乘除取余都是转换成二进制进行位运算

至于为什么要取反再+1这么做,其实是为了提高运算效率,用一套逻辑执行运算。底层就不用再去追究了。

注意:系统最小取反之后是他本身,因为没有正数和他对应; 0取反是0


选择排序

比如给一个数组,要求从小到大排序。

选择排序的步骤:以N表示数组个数,0~N-1表示数组下标。

  1. 数组从0~N-1遍历找出最小值(记为a),把a和数组原来第一个元素对调,这样a就是在数组下标为0的位置了。
  2. 数组从1~N-1遍历找出最小值(记为b),把b和数组原来第二个元素对调,这样b就是在数组下标为1的位置了。
  3. 数组从2~N-1遍历找出最小值...... 以下是选择排序的代码(不唯一)
public class Demo01 {	
	public static void selectSort(int[] arr) {
		if(arr == null || arr.length < 2) 
			return;
		int N = arr.length;		
		// 0~N-1选最小值
		// 1~N-1选最小值
		// 2~N-1选最小值
		// ......
		for (int i = 0; i < N; i++) {
			//这个i就表示找出最小值的范围
			int minValueIndex = i; //记录每一次遍历最小值所在位置
			for (int j = i+1; j < N; j++) { //i以后所有元素都看一遍
				minValueIndex = arr[j] < arr[minValueIndex] ? j : minValueIndex; //如果j位置的元素小于所给的i位置元素就更改最小元素所在位置
			}
			//交换方法
			swap(arr,i,minValueIndex);
		}
	}
	
	public static void swap(int[] arr, int i , int j) {
		int tmp = arr[j];
		arr[j] = arr[i];
		arr[i] = tmp;
	}
	
	public static void printArray(int[] arr) {
		for(int i = 0;i<arr.length;i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}
	
	public static void main(String[] args) {
		int [] arr = {7,1,2,3,5,6,1,8,6,9,3};
		printArray(arr);
		selectSort(arr);
		printArray(arr);
	}
}

冒泡排序

冒泡排序的步骤:N表示数组元素,0~N-1表示下标,a表示数组名

  1. 给定一个乱序数组,先在a[0]和a[1]之间比较大小,谁大排后面
  2. a[1]和a[2]之间比较谁大谁排后面
  3. 一直到a[N-2]和a[N-1]比较完,“谁大谁往后”。
  4. 执行完上面的步骤我们就会发现最大的那个数字就排到了数组最后。到了它应该在的位置。
  5. 下面就是重复步骤1-3,只不过这次是从a[0]到a[N-2],因为a[N-1]已经存了最大的那个数。
public class Demo1 {	
    public static void swap(int[] arr, int i , int j) {
            int tmp = arr[j];
            arr[j] = arr[i];
            arr[i] = tmp;
    }

    public static void bubbleSort(int[] arr) {
            if(arr == null || arr.length < 2) 
                    return;
            int N = arr.length;
            // 0~N-1
            // 1~N-2
            // 2~N-3
            // ......
            for(int end = N-1;end >= 0;end--) {
                    //0与1之间比较,1与2之间比较...end-1与end之间比较,"判断是否交换"
                    for(int second = 1;second <= end;second++) { //second表示比较数之间第二个数,比如0和1之间第二个数是1,1和2之间第二个数是2
                            if(arr[second-1]>arr[second]) { 
                                    swap(arr, second - 1, second); //交换
                            }
                    }
            }
    }

    public static void printArray(int[] arr) {
            for(int i = 0;i<arr.length;i++) {
                    System.out.print(arr[i] + " ");
            }
            System.out.println();
    }

    public static void main(String[] args) {
            int [] arr = {7,1,2,3,5,6,1,8,6,9,3};
            printArray(arr);
            bubbleSort(arr);
            printArray(arr);
    }
}

插入排序

给一个无序数组,a表示数组名,N表示数组元素个数

  1. 从0-0保证有序(0为数组下标)
  2. 从0-1保证有序(即a[0]和a[1]比较大小,如果a[0]比a[1]大,则两个位置的元素互换位置)
  3. 从0-2保证有序(先让a[2]和a[1]比较大小,如果a[1]大于a[2],则让两个位置元素互换;再让a[1]和a[0]比较大小,如果a[0]比a[1]大,则两个位置的元素互换位置)
  4. ......

以下提供两种插入排序代码insertSort和insertSort1

public class Demo1 {
	public static void swap(int[] arr, int i , int j) {
		int tmp = arr[j];
		arr[j] = arr[i];
		arr[i] = tmp;
	}
		
	public static void printArray(int[] arr) {
		for(int i = 0;i<arr.length;i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}
	
	public static void insertSort(int[] arr) {
		if(arr == null || arr.length < 2) 
			return;
		int N = arr.length;
		//0~0 完成
		//0~1
		//0~2
		//...
		//0~N-1
		for(int end = 1;end < N;end++) {
			int newNumIndex = end;
			while(newNumIndex-1 >= 0 && arr[newNumIndex-1] > arr[newNumIndex]) { //end左边有数且左边的数比end位置的数大
				swap(arr,newNumIndex-1,newNumIndex);
				newNumIndex--; //交换之后往左移动
			}
		}
	}
	
	public static void insertSort1(int[] arr) {
		if(arr == null || arr.length < 2) 
			return;
		int N = arr.length;
		for(int end = 1;end < N;end++) {
			for(int pre = end-1;pre >= 0 && arr[pre] > arr[pre+1];pre--) {
				swap(arr,pre,pre+1);
			}
		}
	}
	
	public static void main(String[] args) {
		int [] arr = {7,1,2,3,5,6,1,8,6,9,3};
		printArray(arr);
		insertSort1(arr);
		printArray(arr);
	}
}