目录
时间复杂度
时间复杂度可用大O表示法来表示,O(n)表示算法的增速(算法有多快),n表示算法的操作数,可在理论上表示算法的性能,实际运行时间还需要实际测试。n实际上指c*n,c是算法所需的固定时间量,被称为常量。
时间复杂度通常表示算法的最差情况。
评价一个算法流程的好坏,先看时间复杂度的指标,然后再分析不同数据样本下的实际运行 时间,也就是“常数项时间”。
理解所谓固定时间量。
比如,打印一行数字,第一个算法直接循环打印,第二个算法打印下一个数字前停顿一秒。两个算法的时间复杂度虽然相同,但是实际运行时间是第一个算法快
在O(n)不同的情况下,常量的影响很小
二分查找的时间复杂度是O(log n),简单查找的时间复杂度是O(n),假设列表数据是40亿,二分查找只需要找32次。
在相同的情况下,常量的影响很大
快速排序在一般情况下的时间复杂度是O(n*log n),最坏情况是O(n*n),而合并排序的最差情况是O(n*log n),但在快速排序的时间复杂度和合并排序的时间复杂度相同的情况下,快速排序要比合并排序要快,因为快速排序的常量要小。
冒泡排序和选择排序都是O(n*n),第一轮n-1,第二轮n-2.......以此类推,等差数列求和。
按位运算
异或运算性质
0^N=N,N^N=0,
可以用于数组元素交换. 注意a和b需要分别指向不同的内存块。相当于两个二进制数进行无进位相加。
a=a^b
b=a^b
a=a^b
异或运算为什么满足交换律和结合律
从无进位相加的角度来看,1+1+0=0,奇数个0结果为0,与顺序无关,故满足交换律
偶数个数相异或结果为0,奇数个数相异或结果为1。
提取一个二进制数最右边的1
int rightone = eor&(~eor+1);
一。有关于位运算的算法题
1.一个整型数组中有一个出现了奇数次的同一个数,有其他出现了偶数次的同一个数,问:怎样输出奇数次的那一个数?
思路:偶数个相同数异或运算为0,最后只剩下0和奇数次的那个数进行异或运算,结果为那个数
public class bitwiseOperation {
public static void main(String[] args){
int arr[]={1,22,1,22,22,33,33,5,5,33,33,5,5};
int eor=0;
for (int i:
arr) {
eor^=i;
}
System.out.println(eor);
}
}
2.一个整型数组中有两个出现了奇数次的同一个数,有其他出现了偶数次的同一个数,问:怎样输出奇数次的那两个数?
思路:首先和第一题一样将数组的元素进行异或运算,最终得到ero=a^b;
想求出其中一个就需要思考到a^b不等于0,即必定有一位是1,利用 ero&(~ero+1)式子求出a^b最右边的1,其余为0的二进制数。
从这一位思考,只有偶数次的1和奇数次的1,用if条件(和或者异或运算)判断和异或运算筛选出奇数次的1,它不是a就是b。
不管是a还是b,只要将这个结果与a^b就能得到另一个奇数次了
public class bitwiseOperation1 {
public static void main(String[] args){
int ero=0;
int arr[]={1,2,1,1,2,66,66,66,3,3,66,
3,3,66};
for (int i:
arr) {
ero^=i;//这里得到a^b
}
int OnlyOne=0;
int rightOne=ero&(~ero+1);//注意这个式子不要写错
for (int i:
arr) {
if((rightOne^i)==0)
OnlyOne^=i;//得到a或者b
}
System.out.println(ero^OnlyOne);
System.out.println(OnlyOne);
}
}
冒泡排序
原理:重复遍历序列,每次遍历比较相邻元素并进行交换,直到整个序列有序。
冒泡排序是一种简单直观的排序算法,它的基本思想是重复地遍历待排序序列,每次比较相邻的两个元素,如果顺序错误就交换它们,直到没有再需要交换为止。
时间复杂度为O(n²),推导过程:
- 不管什么情况下,冒泡排序的每一轮都要比较,第一轮比较n-1次,第二轮比较n-2次,到最后一轮比较1次,即(n-1)+(n-2)+.....+1,运用等差数列求和公式得总次数为 N*(N-1) /2.
- 交换次数:0~ N*(N-1) /2,每次比较都有可能进行交换,假设交换的概率为50%,那么总次数为N*(N-1) /4.
- 综上,忽略减1和常数系数,时间复杂度为O(n²)
实际例子
例如,对 {14, 33, 27, 35, 10} 序列进行升序排序(由小到大排序),冒泡排序算法的实现过程是:
- 第一趟遍历:从第一个元素开始,比较相邻的两个元素,如果前者大于后者,则交换它们。这样一趟下来,最大的元素会被放到末尾: {14, 33, 27, 35, 10} -> {14, 27, 33, 10, 35}
- 第二趟遍历:从第一个元素开始,比较相邻的两个元素,如果前者大于后者,则交换它们。这样一趟下来,第二大的元素会被放到倒数第二个位置: {14, 27, 10, 33, 35} -> {14, 10, 27, 33, 35}
- 第三趟遍历:从第一个元素开始,比较相邻的两个元素,如果前者大于后者,则交换它们。这样一趟下来,第三大的元素会被放到倒数第三个位置: {14, 10, 27, 33, 35} -> {10, 14, 27, 33, 35}
- 第四趟遍历:从第一个元素开始,比较相邻的两个元素,如果前者大于后者,则交换它们。这样一趟下来,第四大的元素会被放到倒数第四个位置: {10, 14, 27, 33, 35} -> {10, 14,* 27,* 33,* 35}
- 最后只剩下一个元素了,不需要再比较了。
代码实现
@SuppressWarnings("all") public class BubbleSort { public static void main(String[] args) { int arr[] = {6, 5, 4, 3, 2, 1}; int arr1[] = {6, 5, 4, 3, 2, 1}; Method.bubbleSort(arr); } } class Method{ static void bubbleSort(int arr[]) { for (int j = arr.length - 1; j > 0; j--) {//n-1轮的遍历 for (int i = 0; i < j; i++) { if (arr[i] > arr[i + 1]) {//进行比较 //进行交换 arr[i] = arr[i] ^ arr[i + 1]; arr[i + 1] = arr[i] ^ arr[i + 1]; arr[i] = arr[i] ^ arr[i + 1]; } } } for (int i : arr) { System.out.print(i + " "); } } }
在时间效率上,选择排序略优于冒泡排序,因为冒泡排序在一般情况下的交换次数要多于选择排序。
选择排序
原理:重复进行遍历,每次遍历找到无序序列的最小值,并插入到有序序列的末尾,直到无序序列剩下最后一个元素。
好的,我可以给你一个选择排序的例子。选择排序是一种简单直观的排序算法,它的基本思想是每次从待排序序列中找出最小(或最大)的元素,放到已排序序列的末尾,直到所有元素都排好序。
时间复杂为O(n*n)推导
第一轮比较N-1次,第二轮比较N-2次,直到最后比较一次,比较次数一共是(N-1)*N /2,交换次数为0~N-1次,当一组数据完全逆序且为偶数个时会达到最大交换次数。故时间复杂度为 O(N²)
举例
例如,我们使用选择排序算法对 {14, 33, 27, 10, 35, 19, 42, 44} 完成升序排序,需要经历以下几个步骤:
- 遍历整个待排序序列,从中找到最小值 10 并与第一个元素 14 交换位置: {10, 14, 27, 33, 35, 19, 42, 44}
- 待排序序列变成 {33, 27, 14, 35, 19, 42, 44},从中找到最小值 14 并与 33 交换位置: {10,14,33,35,19,42,27,44}
- 待排序序列变成 {19, 35, 33, 27},从中找到最小值 19 并与第一个元素交换位置: {10,14,27,19,33,35}
- 待排序序列变成 {35,33},从中找到最小值 33 并与第一个元素交换位置: {10,14,27,19,33,35
- 最后只剩下一个元素,不需要再比较了。
代码实现
public class SelectionSort {
public static void main(String[] args){
int arr[]={3,1,2,8,4,6,4,64,1};
SelectionMethod.selectionSort(arr);
}
}
class SelectionMethod{
static void selectionSort(int arr[]){
int n=arr.length;
for(int j=0;j<n-1;j++){//遍历n-1轮
int min=j;//用来标记最小值的下标,一开始默认为未排序序列第一个元素的下标
for(int i=j+1;i<n;i++){//从未排序序列的第二个元素开始与最小值比较,直到数组最后一个元素
if(arr[i]<arr[min]){//必定比较n-i次
min=i;
}
}
if(min!=j) {//如果最小值更新了,则进行交换,如果最小值刚好是未排序的第一个元素的下标,则不用交换。
arr[min] = arr[min] ^ arr[j];
arr[j] = arr[min] ^ arr[j];
arr[min] = arr[min] ^ arr[j];
}
}
for(int i:arr){
System.out.print(i + " ");
}
}
}
插入排序
插入排序的原理:将无序序列的第一个元素一个一个有序地插入到序列中直到遍历到无序序列为空为止。
时间复杂度为O(N²),推导(最坏情况):
数组元素逆序,第一轮需要比较和移动1次,最后一轮需要N-1次,总次数为N*(N-1) / 2,故时间复杂度为O(N²)
但是最好情况却比冒泡排序和选择排序好,因为只需要比较N-1次,而冒泡排序和选择排序必定要比较,三个算法都不用交换。
举例
好的,我可以给你一个插入排序的例子。假设你有一个数组,里面有6个元素,分别是5,3,5,0,1和4。你想把它们按照从小到大的顺序排列。你可以用插入排序的方法来实现。
首先,你把第一个元素5看作是已经排好序的序列,然后从第二个元素开始,依次和前面已经排好序的序列比较,并插入到合适的位置。具体过程如下:
- 第一步:比较第二个元素3和第一个元素5,发现3小于5,所以把3插入到5的前面。此时已经排好序的序列是3和5。
- 第二步:比较第三个元素5和前面已经排好序的序列3和5。发现5大于等于3和5,所以不需要移动位置。此时已经排好序的序列还是3和5。
- 第三步:比较第四个元素0和前面已经排好序的序列3和5。发现0小于3和5,所以把0插入到最前面。此时已经排好序的序列是0、3和5。
- 第四步:比较第五个元素1和前面已经排好序的序列0、3和5。发现1大于0但小于3和5,所以把1插入到0后面、3前面。此时已经排好序的序列是0、1、3和5。
- 第五步:比较最后一个元素4和前面已经排好序的序列0、1、3和5。发现4大于1但小于3和5,所以把4插入到1后面、3前面。此时已经排好序的所有元素是0、1、4、3 和 。
这样就完成了对数组中6个元素进行插入排序的过程。
代码实现
public class SelectionSort {
public static void main(String[] args){
int arr[]={3,1,2,8,4,6,4,64,1};
SelectionMethod.selectionSort(arr);
}
}
class SelectionMethod{
static void selectionSort(int arr[]){
int n=arr.length;
for(int j=0;j<n-1;j++){//遍历n-1轮
int min=j;//用来标记最小值的下标,一开始默认为未排序序列第一个元素的下标
for(int i=j+1;i<n;i++){//从未排序序列的第二个元素开始与最小值比较,直到数组最后一个元素
if(arr[i]<arr[min]){//必定比较n-i次
min=i;
}
}
if(min!=j) {//如果最小值更新了,则进行交换,如果最小值刚好是未排序的第一个元素的下标,则不用交换。
arr[min] = arr[min] ^ arr[j];
arr[j] = arr[min] ^ arr[j];
arr[min] = arr[min] ^ arr[j];
}
}
for(int i:arr){
System.out.print(i + " ");
}
}
}
选择排序和插入排序的区别
插入排序和选择排序的区别主要有以下几点:
- 插入排序是将无序列表中的第一个元素插入到已经排好序的列表中,而选择排序是从无序列表中找出最小(或最大)的元素,和无序列表中的第一个元素交换位置。
- 插入排序在最好情况下(即列表已经有序),只需要比较n-1次,不需要移动元素,时间复杂度为O(n);而选择排序在最好情况下也需要比较n(n-1)/2次,移动n-1次元素,时间复杂度为O(n^2)。
- 插入排序在最坏情况下(即列表逆序),需要比较n(n-1)/2次,移动n(n-1)/2次元素,时间复杂度为O(n^2);而选择排序在最坏情况下也和最好情况一样。
- 插入排序是稳定的算法,即相同值的元素在排序后不会改变相对位置;而选择排序是不稳定的算法,即相同值的元素可能会改变相对位置。
选择排序和冒泡排序和插入排序的区别
选择排序和冒泡排序和插入排序的区别主要有以下几点:
- 选择排序是每次从无序列表中找出最小(或最大)的元素,和无序列表中的第一个元素交换位置,直到所有元素有序;冒泡排序是每次比较相邻的两个元素,如果顺序不对就交换位置,直到所有元素有序;插入排序是每次将无序列表中的第一个元素插入到已经排好序的列表中,直到所有元素有序。
- 选择排序和冒泡排序都需要进行n(n-1)/2次比较,但选择排序只需要进行n-1次交换,而冒泡排序可能需要进行多达n(n-1)/2次交换;插入排序在最好情况下(即列表已经有序),只需要比较n-1次,不需要移动元素,在最坏情况下(即列表逆序),需要比较和移动n(n-1)/2次元素。
- 选择排序和冒泡排序都是不稳定的算法,即相同值的元素可能会改变相对位置;而插入排序是稳定的算法,即相同值的元素在排序后不会改变相对位置。