01 认识复杂度和简单排序算法
#时间复杂度
常数操作举例:
属于常数操作:int a = arr[i];数组中,只是算了一个偏移量;加减乘除;位运算...
不属于常数操作:int b = list.get(i);链表中,只能遍历去找
当两个算法时间复杂度相等时,只能实际去运行来确定哪个算法更优
#选择排序
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到全部待排序的数据元素排完。
过程:看N眼,N次比较,1次swap;看N-1眼,N-1次比较,1次swap.......
看:N+N-1+N-2+... 比较:N+N-1+N-2+... swap:N次
=aN^2+bN+c
不要低阶项,只要高阶项,不要常系数==》O(n^2)
只需要开辟几个变量的额外空间==》O(1)
public static void selectionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length - 1; i++) {//i~N-1
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {//i~N-1上找最小值下标
minIndex = arr[j] < arr[minIndex] ? j : minIndex;
}
swap(arr, i, minIndex);
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
void swap(int arr[], int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
void selectionSort(int arr[],int len) {
if (arr[0] == NULL || len < 2) {
return;
}
for (int i = 0; i < len - 1; i++) {//确定范围为 i~N-1
int minIndex = i;
for (int j = i + 1; j < len; j++) {//i~N-1上找最小值下标
minIndex = arr[j] < arr[minIndex] ? j : minIndex;
}
swap(arr, i, minIndex);
}
}
#冒泡排序
重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int e = arr.length - 1; e > 0; e--) {//规定范围 0~e
for (int i = 0; i < e; i++) {
if (arr[i] > arr[i + 1]) {
swap(arr, i, i + 1);
}
}
}
}
//交换arr的i和j位置上的值
public static void swap(int[] arr, int i, int j) {
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
void swap(int arr[], int i, int j) {
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
void bubbleSort(int arr[],int len) {
if (arr == NULL || len < 2) {
return;
}
for (int e = len - 1; e > 0; e--) {
for (int i = 0; i < e; i++) {
if (arr[i] > arr[i + 1]) {
swap(arr, i, i + 1);
}
}
}
}
#异或运算 ^
相同为0,不同为1:0^0=0 0^1=1 1^0=1 1^1=0
可以理解为无进位相加
- 0^N=0 N^N=0
- 满足交换律和结合律
//交换a和b的值(不用额外变量)
a=a^b;
b=a^b;
a=a^b;
但是前提是:a和b的内存地址不同,地址相同时会被洗成0
题目-数组中找奇数次出现的数
1 在一个数组中,只有一个数出现了奇数次,其他数出现偶数次,怎么找出这个数?
int eor =0; 把eor从数组头异或到数组尾,结束时eor就是出现了奇数次的数(偶次彼此消掉了)
public static void printOddTimesNum1(int[] arr) {
int eor = 0;
for (int cur : arr) {
eor ^= cur;
}
System.out.println(eor);
}
void printOddTimesNum1(int arr[],int len) {
int eor = 0;
for (int i = 0; i < len;i++) {
eor ^= arr[i];
}
cout<<"数组中次数为奇数的数为:"<<eor;
}
异或运算的顺序无所谓的原因:因为异或运算可以看成无进位相加,结果中某一位的值只和所有数的这一位1出现的次数有关,和顺序无关
2 在一个数组中,两种数出现了奇数次,其他数出现偶数次,怎么找出这个数?
int eor =0;设出现奇数次数为a和b,把eor从数组头异或到数组尾,结束时eor就是a^b
所以eor=a^b (a!=b ——》eor!=0)
eor一定在某一位(至少一位)不等于0 ,假设第X位为1,说明a和b在第X位不一样
int eor'=0;把eor’从数组头异或到数组尾,只异或数组中那些X位不为0的数,结束时eor'就是a或者b
X位0的数不影响结果,只异或数组中那些X位不为0的数时,other2中1的个数为偶数次全消除,只剩a或b中一个,eor'只能碰到a或者b中一个,得到的结果就是另外一个
eor^eor'是a或b的另外一个
public static void printOddTimesNum2(int[] arr) {
int eor = 0;
for (int i=0;i<arr.length;i++) {
eor^= arr[i];
}
//eor=a^b
//eor!=0
//eor必然有一个位置是1
int rightOne = eor & (~eor + 1);//提取出最右边的1
int eorhasone=0;//eor'
for (int cur : arr) {
if ((cur & rightOne) != 0) {
eorhasone ^= cur;
}
}
System.out.println(eorhasone + " " + (eor ^ eorhasone));
}
//提取最右边的1
int rightOne = eor & (~eor + 1);
eor: 010100
~eor: 101011
~eor+1: 101100
eor&~eor+1: 000100
void printOddTimesNum2(int arr[],int len) {
int eor = 0;
for (int i = 0; i < len; i++) {
eor ^= arr[i];
}
//eor=a^b
//eor!=0
//eor必然有一个位置是1
int rightOne = eor & (~eor + 1);//提取出最右边的1
int eorhasone = 0;//eor'
for (int i = 0; i < len; i++) {
if ((arr[i] & rightOne) != 0) {
eorhasone ^= arr[i];
}
}
int eor2 = eor ^ eorhasone;
cout << "数组中出现奇数次数为:" << eorhasone << "和" << eor2;
}
#插入排序
对于少量元素的排序,它是一个有效的算法。插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增 1 的有序表 。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动。
插入排序的时间复杂度和数据状况有关
选择排序和冒泡排序和数据状况无关,不影响流程的进行
估计算法时间复杂度时,一律按照算法可能遇到的最差情况估计
public static void insertionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
//0-0已经有序
//将0-i做到有序
for (int i = 1; i < arr.length; i++) { //0-i范围
for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
swap(arr, j, j + 1);
}
}
}
public static void swap(int[] arr, int i, int j) {
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
void swap(int arr[], int i, int j) {
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
void insertionSort(int arr[],int len) {
if (arr == NULL || len < 2) {
return;
}
//0-0已经有序
//将0-i做到有序
for (int i = 1; i < len; i++) { //范围
for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
swap(arr, j, j + 1);
}
}
}
#二分法
题目-局部最小问题
arr数组中,无序,任何两个相邻数一定不相等;找一个局部最小的位置,能否时间复杂度好于O(n)?(局部最小定义:位置为0的数<位置为1的数 或 N-2<N-1 或者 i-1<i<i+1)
二分不一定需要有序
优化一个流程,优化的方向:1.数据状况特殊 2.问题特殊
#对数器
// 和系统排序方法做对比
public static void comparator(int[] arr) {
Arrays.sort(arr);
}
//生成随机数组
public static int[] generateRandomArray(int maxSize, int maxValue) {
//Math.random() [0,1)所有小数,等概率返回一个
//Math.random()*N [0,N)所有小数,等概率返回一个
//(int)Math.random()*N [0,N-1]所有整数,等概率返回一个
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];//长度随机
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());//每个值随机
}
return arr;
}
// for test
public static int[] copy