在找实习公司和校园招聘的过程中遇到了很多有意思的面试题,现在想来完全记住弄明白的也不多,恨当时没有好好做记录啊,现在零零碎碎做个笔记吧。至于答案,一部分是当时没答出来,回去后网上找的,一部分是自己的理解,有所纰漏,欢迎指正。
线程是指进程内的一个执行单元,也是进程内的可调度实体.
与进程的区别:
(1)地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间;
(2)资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
(3)CPU调度:线程是处理器调度的基本单位,但进程不是.
相同点:二者均可并发执行.
进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。进程和线程的区别在于:
简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
线程的划分尺度小于进程,使得多线程程序的并发性高。
另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
第一范式:表中每个域必须是原子的,即每一列都是不可再分的数据项,而不能是集合,数组等。表中的每个域值,只能记录实体的一个属性或者某个属性的某个部分。(不能有表中表)【防止表中表】
第二范式:在符合第一范式的情况下,必须要有主键,即能够选取某一个属性或者某些属性,根据这些属性能够唯一的确定一个元组或者实例。(各个元组必须能唯一区分,否则有太多冗余。)【要有主键,去除相同元组的冗余】
第三范式:在符合第二范式的情况下,表中的任意属性不能,依赖于其他非主属性。(目的也是为了去除表中的冗余。如果依赖其他非主属性的话,可以重新建立一个表。)【如果表可拆分,可以去除冗余】
BC范式:主键(住码)的真子集,不能唯一的确定非主属性。(目的是为了去除主键过于冗余的情况。)【去除主键中的冗余,主键必须是最小的候选码】
1、如何判断是否存在环?
2、如何知道环的长度?
3、如何找出环的连接点在哪里?
4、带环链表的长度是多少?
解法思路:
1、对于问题1,使用追赶的方法,设定两个指针slow、fast,从头指针开始,每次分别前进1步、2步。如存在环,则两者相遇;如不存在环,fast遇到NULL退出。
2、对于问题2,记录下问题1的碰撞点p,slow、fast从该点开始,再次碰撞所走过的操作数就是环的长度s。
3、问题3:有定理:碰撞点p到连接点的距离=头指针到连接点的距离,因此,分别从碰撞点、头指针开始走,相遇的那个点就是连接点。(证明在后面附注)
4、问题3中已经求出连接点距离头指针的长度,加上问题2中求出的环的长度,二者之和就是带环单链表的长度
1,2,3|1,2,4|1,2,5|1,3,4|1,3,5|1,4,5|2,3,4|2,3,5|2,4,5|3,4,5
一共十种情况。现在给出m,n要求输出所有的组合情况。
方法一:利用递归思想。
//从后往前选取,选定位置i后,再在前i-1个里面选取m-1个。 //如 1 2 3 4 5 中选取 3 个 //1、选取5后,再在前4个里面选取2个,而前4个里面选取2个又是一个子问题,递归即可。 //2、如果不包含5,直接选定4,那么再在前3个里面选取2个,而前三个里面选取2个又是一个子问题,递归即可。 //3、如果也不包含4,直接选取3,那么再在前2个里面选取2个,刚好只有两个。 //纵向看,1、2、3刚好是一个for循环,初值为5,终值为m //横向看,该问题为一个前i-1个中选m-1的递归。 void Combination(int arr[], int nLen, int m, int out[], int outLen) { if(m == 0) { for (int j = 0; j < outLen; j++) cout << out[j] << "\t"; cout << endl; return; } for (int i = nLen; i >= m; --i) //从后往前依次选定一个 { out[m-1] = arr[i-1]; //选定一个后 Combination(arr,i-1,m-1,out,outLen); // 从前i-1个里面选取m-1个进行递归 } } void PrintCombination(int arr[], int nLen, int m) { if(m > nLen) return; int* out = new int[m]; Combination(arr,nLen,m,out,m); delete [] out; }
方法二:二进制组合算法:
思路是开一个数组,其下标表示1到m个数,数组元素的值为1表示其下标
代表的数被选中,为0则没选中。
首先初始化,将数组前n个元素置1,表示第一个组合为前n个数。
然后从左到右扫描数组元素值的“10”组合,找到第一个“10”组合后将其变为“01”组合,同时将其左边的所有“1”全部移动到数组的最左端(只有第一位变为0才需要移动,否则其左边的1本来就在最左端,无需移动)。
当第一个“1”移动到数组的m-n的位置,即n个“1”全部移动到最右端时,就得到了最后一个组合。
例如求5中选3的组合:
1 1 1 0 0 //1,2,3
1 1 0 1 0 //1,2,4
1 0 1 1 0 //1,3,4
0 1 1 1 0 //2,3,4
1 1 0 0 1 //1,2,5
1 0 1 0 1 //1,3,5
0 1 1 0 1 //2,3,5
1 0 0 1 1 //1,4,5
0 1 0 1 1 //2,4,5
0 0 1 1 1 //3,4,5
代码如下:
void Combine(int arr[], int n, int m) { if(m > n) return; int* pTable = new int[n]; //定义标记buf并将其前m个置1 memset(pTable,0,sizeof(int)*n); for(int i = 0; i < m; ++i) pTable[i] = 1; bool bFind = false; do { for (int i = 0; i < n; i++) //打印当前组合 { if(pTable[i]) cout << arr[i] << "\t"; } cout << endl; bFind = false; for(int i = 0; i < n-1; i++) { if(pTable[i]==1 && pTable[i+1]==0) { swap(pTable[i],pTable[i+1]); //调换10为01 bFind = true; if(pTable[0] == 0) //如果第一位为0,则将第i位置之前的1移到最左边,如为1则第i位置之前的1就在最左边,无需移动 { for (int k=0, j=0; k < i; k++) //O(n)复杂度使1在前0在后 { if(pTable[k]) { swap(pTable[k],pTable[j]); j++; } } } break; } } } while (bFind); delete [] pTable; }
索引类似数组下标,查询时复杂度为O(1),在SQL语句中使用索引避免了对数据库记录的遍历。
区别:
1:主键是为了标识数据库记录唯一性,不允许记录重复,且键值不能为空,主键也是一个特殊索引.
2:数据表中只允许有一个主键,但是可以有多个索引.
3.使用主键会数据库会自动创建主索引,也可以在非主键上创建索引,方便查询效率.
4:索引可以提高查询速度,它就相当于字典的目录,可以通过它很快查询到想要的结果,而不需要进行全表扫描.
5:除了主键索引外索引的值可以为空.
6:主键也可以由多个字段组成,组成复合主键,同时主键肯定也是唯一索引.
7:唯一索引则表示该索引值唯一,可以由一个或几个字段组成,一个表可以有多个唯一索引.