算法与数据结构
一、时间复杂度和空间复杂度[百度百科]
-
时间复杂度
算法的时间复杂度是一个函数,它定性描述该算法的运行时间。这是一个代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。
时间复杂度取的是算法中最差的时间复杂度。
-
空间复杂度
空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度,记做S(n)=O(f(n))。比如直接插入排序的时间复杂度是O(n^2),空间复杂度是O(1) 。而一般的递归算法就要有O(n)的空间复杂度了,因为每次递归都要存储返回信息。一个算法的优劣主要从算法的执行时间和所需要占用的存储空间两个方面衡量
二、排序算法
2.1 选择排序
选择排序是将最值移动到最左端。时间复杂度O(N^2),空间复杂度O(1)[临时变量[用于交换数据]
它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。
// 选择排序
public void selectSored() {
// 选择排序
int[] arr = new int[]{
4, 2, 5, 6, 1, 3, 7, 3, 8};
// 遍历所有位置上的数字与后面的值进行比较
for (int i = 0; i < arr.length; i++) {
// 将当前位置的数值与后面的值进行比较,如果小于当前值则进行交换
for (int j = i + 1; j < arr.length; j++) {
if (arr[i] > arr[j]) {
int tem = arr[i];
arr[i] = arr[j];
arr[j] = tem;
}
}
}
}
2.2 冒泡排序
冒泡排序相邻两个数字进行比较,将最值放到最右端。时间复杂度为O(N^2),空间复杂度O(1)
它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。
// 冒泡排序
public void BubbleSorted() {
// 冒泡排序
int[] arr = new int[]{
4, 2, 5, 6, 1, 3, 7, 3, 8};
// 控制循环此时
for (int i = 0; i < arr.length; i++) {
// 比较相邻两个值的大小,最后一个值不能的下标j不能和j+1进行比较,否则会数组下标越界,所以最大的取值范围是【j < arr.length - i - 1】
for (int j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j, j + 1);
}
}
}
}
// 通过异或运算巧妙的实现两个值进行交换(注意:这个两个值对应的内存地址不能一致,否则异或运算的值为0)
public void swap(int[] arrs, int i, int j) {
arrs[i] = arrs[i] ^ arrs[j];
arrs[j] = arrs[i] ^ arrs[j];
arrs[i] = arrs[i] ^ arrs[j];
}
异或运算(无进位相加运算):相同为0,不同为1。异或运算满足交换律和结合律。a = 0^a;0 = a^a。
根据异或运算的特性,可以解决下面两道问题。
-
给定一个整数数组,有一个数字为奇数个,其他的数字为偶数个,找出这个奇数个的数字是多少?
public void test2() { int[] arrs = new int[]{ 3, 2, 4, 3, 3, 3, 2, 5, 7, 7, 5}; int tem = 0; for (int arr : arrs) { tem ^= arr; } System.out.println(tem); }
-
给定一个整数数组,有两种数字为奇数个,其他的数字为偶数个,找出这两个奇数个的数字是多少?
/** * 解题思路 * 假设这两种奇数个的值为a和b * 遍历异或运算,根据异或运算的特性,遍历完成之后我们可以得到a^b * a≠b,可以想到指定位置(二进制)上肯定有一个为1有一个为0 * 我们将整个数组分为两类,一类是指定位置上的值为1,一类是指定位置上的值为0 * 我们提取出指定为上为1的这类值进行异或运算,最终得到的肯定是a * 然后再根据异或运算可以得到b了(b = a^b^a) */ 遍历异或运算可以得到这两种奇数的异或值 a^b public void test3() { // 假设这两个值为a,b int[] arrs = new int[]{ 2, 3, 4, 5, 6, 1, 2, 3, 1, 5, 6, 7}; int tem = 0; for (int arr : arrs) { tem ^= arr; } // 此时tem = a ^ b // 找到最右侧的1(二进制) int tem1 = tem & (~tem + 1); int t = 0; for (int arr : arrs) { // 将数组的值分为两类,一类是指定位置上(比如第三位)上为1,一类是指定位置上不为1。假设a为指定为位置上为1,将这一类全部提取出来((tem1 & arr) == 1)进行异或运算,最后得到的肯定是a if ((tem1 & arr) == 1) { t ^= arr; } } // 此时t为a或者b System.out.println(t + " " + (tem ^ t)); }
总结:如何获取一个数最右端的1:(a & (~a + 1)
2.3 插入排序
插入排序:它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动。时间复杂度O(N2),空间复杂度O(1)。插入排序相对于选择排序和冒泡排序是较优选择,根据数据状况不同,最优的时间复杂度是O(N),最差的时间复杂度为O(N2)
public void test4() {
// 选择排序
int[] arrs = new int[]{
4, 2, 5, 6, 1, 3, 7, 3, 8};
// 从数组的第二个位置开始,因为数组的一个值肯定是有序的
for (int i = 1; i < arrs.