- http与https的区别
1. http(text transport ) 超文本传输协议——基于传输层的tcp,https 安全套接的http(ssl secure scoket layer),需要用到证书
2. url上的区别: http://,https://
3. 安全,http明文传输,不安全,https加密传输,安全,C向S端发送https()请求,S端返回包含公钥的证书,C端随机生成一个对称密钥,并使用公钥对密钥加密,再将加密后的对称密钥传输给S端,
4. 端口号不同,http的端口号是:80,https是403
5. https协议的作用分为两种:1.确认网站的真实性;2.建立一个信息安全通道 - 手写数据库的连表查询——查询两张表的共同字段并统计
- 排序算法
空间复杂度,时间复杂度
各类排序算法的优缺点,
排序算法 | 平均时间复杂度 | 最好时间复杂度 | 最差时间复杂度 | 空间复杂度 | 稳定性 | 排序方式 |
---|---|---|---|---|---|---|
选择排序 | 1 | 1 | 1 | 1 | 内排 | |
快速排序 | 1 | 1 | 1 | 1 | 1 | |
堆排序 | 1 | 1 | 1 | 1 | 1 | |
冒泡排序 | O(n^2) | O(n) | O(n^2) | 1 | 1 | |
希尔排序 | 1 | 1 | 1 | 1 | 1 | |
归并排序 | 1 | 1 | 1 | 1 | 内排 | |
插入排序 | 1 | 1 | 1 | 1 | 内排 | |
桶排序 | 1 | 1 | 1 | 1 | 1 | |
基数排序 | 1 | 1 | 1 | 1 | 内排 | |
计数排序 | 1 | 1 | 1 | 1 |
1. 排序的稳定性:a=b,如果在未排序之前a在b之前,则排序后a也在b之前
2. 内排序:整个排序过程都在内存中进行
3. 外排序:由于数据太大,将整个数据放在磁盘中,而排序则是通过磁盘与内存的数据交换完成
4. 常见的快速排序,归并排序,堆排序,冒泡排序属于比较排序,在排序的最终结果里,
元素之间的次序依赖于他们之间的比较,每个数都必须和其他数进行比较
6. 冒泡排序
冒泡排序稳定,因为比较两个相邻的元素,若相等则顺序不变
最优时间复杂度:O(n)——虽然不用交换,但是按常理来还是需要比较n^2次,但在理想条件下,
我们会修改代码(增加一个flag,第一次比较完,就直接退出),
最差时间复杂度:(3n(n-1))/2 这个是在最优时间复杂度为 (n(n-1))/2 的条件下的得来的,
3倍为交换的三个步骤
平均时间复杂度:按照上面的最优和最差,2(n(n-1))
1. 算法实现:
//从小到大,实现过程是不断将大的数往后挪,看似好像把小的数往前移动,其实每一次
移动之后,较小的数就不会参与下一次比较,所以j的取值范围才是
array.length-1-i ,这里的i就是整个比较的轮次,每一轮都会将这一轮参与
比较的最大数移到最后
public static int[] bubbleSort(int[] array){
if(array.length==0){
return array;
}
for(int i=0;i<array.length;i++){
for(int j=0;j<array.length-1-i;j++){
if(array[j+1]<array[j]){
int temp = array[j+1];
array[j+1] = array[j];
array[j] = temp;
}
}
}
return array;
}
7. 选择排序
最优时间复杂度:O(n^2)——必定遍历所有,
最差时间复杂度:(3n(n-1))/2 这个是在最优时间复杂度为 (n(n-1))/2 的条件下的得来的,
3倍为交换的三个步骤
平均时间复杂度:按照上面的最优和最差,2(n(n-1))
不稳定
5 8 5 2 9
2 8 5 5 9//注意这里的5 5就不稳定
1. 算法实现:
//找到最小的数,将它换去第一位,然后再从下一位就开始找
public static int[] selectionSort(int[] array){
if(array.length==0){
return array;
}
for(int i=0;i<array.length;i++){
int midIndex = i;
for(int j = i;j<array.length;j++){
if(array[j] < array[midIndex] ){ //找到最小数
midIndex = j;//将最小数的索引保存
}
int temp = array[midIndex];
array[midIndex] = array[i];
array[i] =temp;
}
}
return array;
}
8. **插入排序** (原理简单,但实现有点绕)
插入排序稳定,因为元素“插入”是从后往前的
算法实现:
//将第一个元素视为最开始已排好的,然后第二个元素与第一个元素比较,然后是的
第三个与前面两个进行比较,依次从最后向前扫描,直到结束,
//这种排序的特点就是,每一轮排完序之后,前 i+2 个数都是有顺序的
public static int[] insertSort(int [] array){
if(array.length == 0){
return array;
}
int current;
for(int i = 0;i < array.length ; i++){
current = array[i+1];//current指当前要插入的数
int preIndex = i;//preIndex指上一轮排序后最后那个数对应的序号,
//如果要排入的数 < 前面那个数,则将前面那个数复制给后一位,
在这个过程中current始终不变
while (preIndex >= 0&& current <array.[preIndex]){
array[preIndex +1] = array [preIndex];
preIndex--;
}
array[preIndex + 1] = current;//
}
retunr array;
}
9. 希尔排序
算法实现:
//希尔排序是在插入排序的基础上改进得到的,相当于是将一个数组分成多组,均使用
public static int[] shellSort(int[] array){
if(array.length==0){
return array;
}
int len = array.length;
int tem,gap = len/2;//gap初始增量,即组数
while(gap > 0){
for(int i = gap;i<len;i++){
tem = array[i];
int preIndex = i - gap;
while(preIndex>=0 && arry[preIndex] > tem){
array[preIndex + gap] = array[perIndex];
preIndex -=gap;
}
array[preIndex + gap] = tem;
}
gap/=2;
}
return array;
}
10. 归并排序
归并排序与选择排序一样,性能不受输入数据的影响,但需要额外的内存空间
算法实现
先将数组一分为二,然后合并两个有序数组——注意这里的有序数组是由数组不断二分,
最终只剩一个元素,然后调用第二部分合并得到
//第一部分,将不断数组一分为二
pubic static int[] MergeSort(int[] array){
if(array.length < 2 ) return array;
int mid = array.length/2;
int[] left = Arrays.copyOfRange(array,0,mid);
int[] right = Arrays.copyOfRange(array,mid,array.length);
return merge(MergeSort(left), MergeSort(right)); //递归
}
//排序,
public static int[] merge(int[] left,int[] right){
int[] result = new int[left.length + right.length];
for(int index = 0,i=0,j=0;index < result.length; index++){
if(i >= left.length)
result[index] = right[j++];
else if(j >= right.length)
result[index] = left[i++];
else if(left[i] > right[i])
result[index] = right[j++];
else
result[index] = left[i++];
}
return result;
}
11. 快速排序
算法实现
//选择数组中的一个数,标记为X=a[n],排在他后面但比他小的数,与他交换位置,
排在他前面比他大的数与他交换位置
public static int[] QuickSort(int[] array, int start, int end){
if(array.length < 1||start < 0 || end >= array.length||start > end)
return null;
int smallIndex = partition(array,start,end);
if(smallIndex > start)
QuickSort(array,start,smallIndex- 1);
if(smallIndex < end)
QuickSort(array,smallIndex +1, end);
return array;
}
//partition ——快速排序算法
public static int partition(int[] array, int start,int end){
int pivot = (int)(start + Math.random()*(end - start +1));
int smallIndex = start -1;
swap(arrsy,pivot,end);
for(int i=start;i<=end;i++){
if(array[i] = array[end]){
smallIndex++;
if(i > smallIndex){
swap(array,i,smallIndex);
}
}
}
return smallIndex;
}
public static void swap(int[] array,int i ,int j){
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
12. 堆排序
堆排序是一种选择排序,不稳定排序
将待排序序列构造成一个大顶堆,此时整个序列的最大值就是堆顶的根节点——从最后一个
非叶子节点从左往右,从下往上,交换非叶子节点与其子节点的值,保证非叶子节点的值最大
大顶堆:每个结点的值都大于或等于其左右子结点的值
叶子节点:没有子节点(度为0)的节点
将堆顶的根节点与末尾元素进行交换,此时末尾就为最大值,然后将剩余n-1个元素重新
构造成一个堆,这样就会得到n个元素的次小值,反复执行就能得到一个有序序列
13. 计数排序
使用一个额外的数组A,其中第i个元素是原数组中值为i的元素的个数,
例如, 2 3 1 2 1 2 3 4 2 3 3 2 4 2
则数组A:2 6 4 2
算法实现
public static int[] CountingSort(int[] array){
if(array.length == 0) return array;
int bias ,min =array[0],max = array[0];
for(int i=1;i< array.length;i++){
if(array[i]>max){
max = array[i];
}
if(array[i]<min){
min =array[i];
}
}
bias = 0-min; //bias是指最小值与0之间的距离,因为要保证bucket的
int[] bucket = new int[max - min + 1];
Arrays.fill(bucket,0);
for(int i =0;i<array.length;i++){
bucket[array[i]+bias]++;
}
int index=0,i=0;
while(index<array.length){
if(bucket[i] != 0){
array[index] = i-bias;
bucket[i]--;
index++;
}
else
i++;
}
return array;
}
14. 桶排序——计数排序的升级版,会涉及多个桶
15. 基数排序——先按个位排序,再按十位排序(先按低优先级排序,再按高优先级)
16. 交换排序与简单选择排序
每次比较完都要交换——交换排序
比较晚一个循环再交换——简单选择排序
-
手撕二叉树
-
C#——微软开发的这款完全面向对象,取消了面向过程,因为之前已经有了C,C++所以
-
数据库基本操作
-
怎样检查内存泄漏,以及如何解决
-
邮件用哪种协议
-
c和java的区别,java的缺点
-
1分钟自我介绍
-
对加班的看法
-
为什么选择多益
-
最近看的哪本技术书籍,有什么感想
-
如果和技术主管的意见不合
-
20%的人做了80%的工作
-
栈溢出
-
平衡二叉树,插入某个元素的时间复杂度
-
增删改查 sql
-
2080原则
-
如何构建一个大小可变的数组
-
软件的可维护性与可重构性
-
gc是如何运行的
garbgecollection 因为编程人员容易忘记内存释放,所以需要gc,java中gc是自动的,gc其实还是对对象的管理,只要内存中某个对象没有了引用1. 引用计数法 2. 分代的回收策略,新生代,老生代——上面所说的的那样,回收的还是对象,而对象的生命周期是不一致的,如果对不同生命周期的对象采用同一种回收方式,如临时变量与线程,若不进行对象存活时间的区分,每次都需要遍历所有对象,而这种对长生命周期的对象的遍历没有意义,会浪费大量的时间
什么时候回收
1. 系统自身决定,
2. 显式调用System.gc()
3. 老年代,持久代被装满 -
tcp三次握手
流程是怎么样的
到底TCP是什么
TCP提供一种可靠,面向连接,字节流,传输层的服务,采用三次握手来建立连接,四次挥手来断开连接
所谓的连接,其实就是C与S之间在内存中保留一份对方的信息,如端口,ip号
第一次握手,C向S端发送syn包(synchronize sequence numbers=1,请求建立连接),并进入SYN_SEND状态,
第二次握手,S在收到syn包后,反馈,ACK=1,确认有效,表示已确认,SYN=1,表示已同意,进入SYN_RECV
第三次握手,C在收到S的SYN+ACK,向服务器发送确认包ACK,发送完毕后,C与S进入ESTABLISHED
为什么是三次,而不是两次甚至四次
三次握手完成了两项功能:1. 确认双方都做好数据交换的准备;1. 其次在握手过程中确定了初始序列号
二次握手就会省略掉第三步,也就是说,在第二次握手之后,也就是S在收到C的SYN,并反馈SYN+ACK之后,S端认为连接已建立,而C端在应答丢失的情况下,将不知道S端是否已准备完毕,甚至是否收到自己的请求,此时C端认为连接还未建立,会忽略掉S端的任何数据传输,只等待确认应答,当S端的发出应答超时时,就只会重复发送同样的数据分组,这样就形成了死锁。
死锁——在两个或多个线程并发执行的过程中,因争夺资源而陷入僵局——进程1在执行过程中,需要调用进程2正在使用多个资源,而进程2在未释放当前资源之前又继续调用进程1所占资源
死锁的一些结论
1. 两个或两个以上的进程的参与
2. 参与死锁的所有进程都在等待资源
3. 参与死锁的进程中至少有两个已经占有资源(抢夺)
4. 死锁进程是当前所有进程中的一个子进程
5. 死锁会造成系统资源的浪费,甚至导致系统崩溃
6. 死锁的结果——程序既不会抛异常,也不会终止,无法继续,但是线程的最终目的是死亡,因此这种状态是需要避免的
死锁与饥饿
1. 死锁与饥饿都是在等待资源
2. 死锁进程占有资源, 饥饿是指一个进程一直得不到资源
死锁的四个必要条件(必要条件是大条件)——用来判断死锁是否发生,如果发生,必须全部满足,只要有一个不满足就不会发生
1. 互斥条件——进程要求所分配资源进行排他性控制,即,一段时间内某资源仅能为一个进程所用——我用你就不能用
2. 不可剥夺——进程所使用资源在未被释放前,不能被强行夺走——我不给,你就不能抢
3. 请求与保持条件——进程已经保持(占有)一个资源,但又提出新的资源请求,而该资源已被占用,此时请求阻塞,但是自己的资源还在保持
4. 循环等待条件——若干进程之间形成资源等待圈
预防死锁:
针对四个必要条件
1. 互斥条件不可打破
2. 某高优先级线程强占有资源时,可以抢占另一个线程,要求它释放资源
请求资源失败,则被强制释放
3. 破坏请求——在分配资源时,完全分配所需,或什么也不分配——一次性分配
破坏保持——在请求其他资源是,必须先释放所占资源
4. 破坏循环等待——给所有资源编号,线程在任意时刻都可以请求资源,但必须按照资源的编号顺序提出
解除死锁:
1. 资源剥夺——将足够的其它资源分配给其他死锁进程,解除死锁状态
2. 进程撤销——人为终止多个死锁进程
3. 进程回退——回退到可以回避死锁地时间节点,自动释放资源
小计:按他的说法,一个人应该是半小时左右