您的位置:首页 > 编程语言 > C语言/C++

C-C++面试知识点总结(三)

2017-08-14 22:53 465 查看
目录:

一、基础知识

1.C/C++

2.STL

3.数据结构与算法

4.计算机网络

5.操作系统

6.数据库

二、项目经历

1.纯属个人YY

3.数据结构与算法基础

(1).基本的线性表的实现

顺序表:用数组实现,因为空间连续,所以叫做顺序表。

链表:用不连续的节点连接成一条链,所以叫做链表。

(2).双链表的实现

双链表:链表中最复杂,但是也是最高效的链表。

双链表因为维护了头指针和尾指针,以及前驱后继指针,所以在添加、插入、删除的时间复杂度都是O(1)。

(3).会用递归

递归:其实递归理解起来并不难,无疑就是自己调用自己,这是一个函数调用的特例,唯一难的是不懂如何活学活用。

当一个问题能划分为N个子问题去解决,那么就考虑用递归。你不需要去纠结其中的细节(当初我就是钻进去了,一直想它为什么会这样,其实用递归只需要它能这样就够了)

(4).字符串匹配

常用的就是BF(暴力算法)、KMP和BM算法。BF就是循环一个个匹配,这会进行很多次重复的比较。而KMP利用一个next数组来指导匹配,尽可能的将比较效率提高。然而经过测试,我发现当数据比较随机的时候(重复率低),BF的速度要高于KMP。所以KMP并不稳定。BM算法就很好的解决KMP的问题,所以BM算法的效率很高,其主要思想是每次后移的位数选择“坏字符规则”和“好后缀规则”两个的较大值。

KMP和BM算法对比参考http://blog.csdn.net/v_JULY_v/article/details/6545192这篇文章

(5).完全二叉树

这是满二叉树的子集,即满二叉树是完全二叉树。

满足的特点为:

1)只允许最后一层有空缺结点且空缺在右边,即叶子结点只能在层次最大的两层上出现;

2)对任一结点,如果其右子树的深度为j,则其左子树的深度必为j或j+1。 即度为1的点只有1个或0个

应用举例:堆排序中所用的数据结构就是完全二叉树。

(6).二叉查找树

这是一颗排序的树,里面的数据按照中序遍历就能得到从小到大的顺序排列。(一般而言,都假设左<根<右)

规则:

对于每一颗子树都有,左节点的值 < 根节点的值 < 右节点的值

没有键值相等的节点(可以加个count计数就行)

(7).平衡二叉树

平衡二叉树实现的方法也有很多种,例如红黑树、AVL、替罪羊树、Treap、伸展树等。

1)其中AVL是最早发明自平衡二叉查找树算法,它是一颗高度平衡的二叉树,在AVL中任何节点的两个子树的高度最大差为一。即|Height(left) - Height(right)| < 2 。

其核心是旋转树,分为单左旋转、单右旋转、左右旋转、右左旋转,目的就是让旋转后的树的任意|Height(left) - Height(right)| < 2 ,使其高度平衡。n个结点的AVL树最大深度约1.44log2n。

2)红黑树(RB-Tree)的实现就比较复杂,其核心就是保持从根节点到叶节点的路径上,黑节点的个数相等,这样就能保持树的比较平衡。其特点如下:
a.每个结点要么是红的,要么是黑的。
b.根结点是黑的。
c.每个叶结点(叶结点即指树尾端NIL指针或NULL结点)是黑的。
d.如果一个结点是红的,那么它的俩个儿子都是黑的。
e.对于任一结点而言,其到叶结点树尾端NIL指针的每一条路径都包含相同数目的黑结点。

3)AVL树和RB树的效率比较

如果说单论查找效率的话,AVL树绝对是优于RB树的,但是实际应用上,你不仅仅只有查找而没有添加、插入、删除等操作,在添加操作的时候,AVL树最坏的情况需要从插入的那个位置开始递归的向上旋转树,且因为需要高度的平衡,所以旋转操作必定会很多。而RB树只需要少量的旋转,以及修改节点颜色即可,从这点看,当数据量大的时候,明显RB树的统计性能比AVL好,这也是为什么STL关联式容器都选用RB树作为底层实现。


(8).B树

英文B-Tree,所以B-树和B树是等价的。为什么要需要B树这种数据结构?因为当数据量大于内存的时候,需要将数据存放到硬盘上,然后再进行数据的检索。若二叉树的深度过大,造成磁盘IO读写次数过大,效率就会低下。B树与红黑树最大的不同在于,B树的结点可以有许多子女,从几个到几千个。那为什么又说B树与红黑树很相似呢?因为与红黑树一样,一棵含n个结点的B树的高度也为O(lgn),但可能比一棵红黑树的高度小许多,应为它的分支因子比较大。所以,B树可以在O(logn)时间内,实现各种如插入(insert),删除(delete)等动态集合操作。

参考这篇文章http://blog.csdn.net/v_july_v/article/details/6530142

(9).B+树

这是一种应文件系统所需而出的一种B树变形树。严格意义来讲它已经不是树了,因为叶子结点都已经相连起来了。

B+树是B树的一种变形,它把所有的数据都存储在叶节点中,内部节点只存放关键字和孩子指针,因此最大化了内部节点的分支因子,所以B+树的遍历也更加高效(B树需要以中序的方式遍历节点,而B+树只需把所有叶子节点串成链表就可以从头到尾遍历)。B+树特别适合带有范围的查找,因为只需要查找到第一个符合的关键字,然后再在叶子结点按顺序查找到符合范围的所有记录即可。

(10).B*树

B*树是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针,将结点的最低利用率从1/2提高到2/3。

(11).DFS深度优先搜索

图的一种搜索算法,但是其可以用在树中。它的思想是,一路走到黑再回头再走到黑。直到所有的节点都被访问过之后结束。从思想上可以看出,必须要一个标志数组来标记这个节点已经被访问过了。

官方的遍历方法定义:

(1)访问顶点v;
(2)依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问;
(3)若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止。


优点:可以快速判断是否有解。

缺点:找最优解的实现比BFS复杂很多

(12).BFS广度优先搜索

故名思意是横向搜索的,所以肯定可以保存其路径信息。其思想是,从某一个节点出发,像周围扩散(上下左右的去遍历),直到所有的节点都遍历完为止。

官方的遍历方法定义:

a.首先将根节点放入队列中。
b.从队列中取出第一个节点,并检验它是否为目标。
b.a.如果找到目标,则结束搜寻并回传结果。
b.b.否则将它所有尚未检验过的直接子节点加入队列中。
c.若队列为空,表示整张图都检查过了——亦即图中没有欲搜寻的目标。结束搜寻并回传“找不到目标”。
d.重复步骤2。


优点:可以轻松找到最优解。

缺点:需要遍历完整张图。

DFS和BFS比较:一般说来,能用DFS解决的问题都能用BFS解决。DFS通过递归实现,易于实现,DFS的常数时间开销会比较少,所以大多数情况下优先考虑DFS实现。

(13).二分搜索

这个要求要查找的序列是有序的,一开始先定位到中间位置,然后依次比较,以两倍的速度缩小范围,直到left>=right为止,其时间复杂度为O(logN)。

例子:

int binary_search(int* a,int n,int value)
{
int left = 1, right = n,mid = 0;
while (left < right)
{
mid = (left + right) / 2;
if (a[mid] == value)
return mid;
else if (a[mid] < value)
left = mid;
else
right = mid;
}
return -1;
}


(14).分治法

分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。

使用分治法的特征:

1) 该问题的规模缩小到一定的程度就可以容易地解决

2) 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。

3) 利用该问题分解出的子问题的解可以合并为该问题的解;

4) 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。


具体的参考这篇文章:

https://www.cnblogs.com/steven_oyj/archive/2010/05/22/1741370.html

(15).动态规划算法

参考这篇文章http://www.hawstein.com/posts/dp-novice-to-advanced.html

(16).回溯法

参考这篇文章http://blog.csdn.net/daniel_ustc/article/details/17040315

(17).双指针

就是两个用指针搞事情。
应用场景:
a.用来判断链表是否有环以及寻找环入口
b.数组寻找范围
c.链表或者数组中移除重复的元素
d.用来找中点或中位数
e.倒数第n个
f.拆分链表


参考这篇文章http://www.cnblogs.com/byrhuangqiang/p/4708608.html

(18).扫描线

参考这篇文章http://blog.csdn.net/orbit/article/details/7368996

(19).字典树(Trie树)

一种用来词频统计、前缀匹配的树。

基本性质为:
1)根节点不包含字符,除根节点外每一个节点都只包含一个字符。
2)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
3)每个节点的所有子节点包含的字符都不相同。


参考文章

https://github.com/julycoding/The-Art-Of-Programming-By-July/blob/master/ebook/zh/06.09.md

(20).冒泡排序

/****************************************
* function          冒泡排序             *
* param     a[]     待排序的数组          *
* param     n       数组长度              *
* return            无                   *
* good time         O(n)                *
* avg time          O(n^2)              *
* bad time          O(n^2)              *
* space             O(1)                *
* stable            yes                 *
*****************************************/
void BubbleSort(int a[],int n)
{
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (a[i] < a[j])
swap(a[i], a[j]);
}
}
}


(21).选择排序

/****************************************
* function          选择排序法           *
* param     a[]     待排序的数组          *
* param     n       数组长度                *
* return            无                   *
* good time         O(n^2)              *
* avg time          O(n^2)              *
* bad time          O(n^2)              *
* space             O(1)                *
* stable            no                  *
*****************************************/
void SelectSort(int a[], int n)
{
int min = 0;
for (int i = 0; i < n; i++)
{
min = i;
for (int j = i + 1; j < n; j++)
{
if (a[j] < a[min])
min = j;
}
if (min != i)
swap(a[min], a[i]);
}
}


(22).插入排序

/****************************************
* function          插入排序法           *
* param     a[]     待排序的数组          *
* param     n       数组长度                *
* return            无                   *
* good time         O(n)                *
* avg time          O(n^2)              *
* bad time          O(n^2)              *
* space             O(1)                *
* stable            yes                 *
*****************************************/
void InsertSort(int a[], int n)
{
int temp = 0;
for (int i = 1,j = 1; i < n; i++)
{
j = i;
temp = a[i];  //待插入的数,假设第一个已经排好序
while (j > 0 && temp < a[j - 1])a[j] = a[j - 1],--j; //向前逐个比较,若该位置插入不了,则往后移
a[j] = temp; //插入
}
}


(23).快速排序

/****************************************
* function          快速排序法--分区       *
* param     a[]     待分区的数组          *
* param     left    区间左             *
* param     right   区间右             *
* return            返回新的基准keyindex  *
*****************************************/
int Partition(int a[], int left, int right)
{
int midIndex = left;
while (left < right)
{
//从右往左扫描,若找到比基准key小的数,则与a[midIndex]交换
while (left < right && a[right] >= a[midIndex])
--right;
if (left < right)
{
swap(a[right], a[midIndex]);
midIndex = right;
}
//从左往右扫描,若找到比基准key大的数,则与a[midIndex]交换
while (left < right && a[left] <= a[midIndex])
++left;
if (left < right)
{
swap(a[left], a[midIndex]);
midIndex = left;
}
}
Print(a, 10);
return midIndex;
}

/****************************************
* function          快速排序法           *
* param     a[]     待分区的数组          *
* param     left    区间左             *
* param     right   区间右             *
* return            无                   *
* good time         O(N*logN)           *
* avg time          O(N*logN)           *
* bad time          O(N^2)              *
* space             O(NlogN)            *
* stable            no                  *
*****************************************/
void Quick_Sort(int a[], int left, int right)
{
if (left < right)
{
//1.随机取基准值,然后交换到left那里
//      srand(GetTickCount());
//      int m = (rand() % (right - left)) + left;
//2.取前中后的中值,然后交换到left那里
//      int m = Mid(left, (left + right / 2), right);
//      swap(a[m], a[left]);
int midIndex = Partition(a, left, right); //获取新的基准keyindex
Quick_Sort(a, left, midIndex - 1);  //左半部分排序
Quick_Sort(a, midIndex + 1, right); //右半部分排序
}
}

void QuickSort(int a[], int n)
{
Quick_Sort(a, 0, n - 1);
}


(24).归并排序

/****************************************
* function          堆调整(大顶堆)        *
* param     a[]     待调整的数组          *
* param     n       数组  大小              *
* return            无                   *
*****************************************/
void HeapAdjust(int a[],int i,int n)
{
int left = 2 * i + 1;    //左孩子
int right = 2 * (i + 1); //右孩子
int maxIndex = 0;
//如果左孩子是叶子节点,并且该节点比父节点大,则交换
maxIndex = (left < n && a[left] > a[i]) ? left : i;
maxIndex = (right < n && a[right] > a[maxIndex]) ? right : maxIndex;
//如果maxIndex != i,则说明要进行交换
if (maxIndex != i)
{
swap(a[i],a[maxIndex]);
//继续递归调整,直到小的都沉到下面去
HeapAdjust(a, maxIndex, n);
}
}

/****************************************
* function          建堆(大顶堆)     *
* param     a[]     数组                  *
* param     n       数组  大小              *
* return            无                   *
*****************************************/
void MakeHeap(int a[],int n)
{
//建大顶堆
for (int i = n / 2 - 1; i >= 0; i--)
HeapAdjust(a, i, n);
}

/****************************************
* function          堆排序法                *
* param     a[]     待排序的数组          *
* return            无                   *
* good time         O(NlogN)            *
* avg time          O(NlogN)            *
* bad time          O(NlogN)            *
* space             O(NlogN)            *
* stable            no                  *
*****************************************/
void HeapSort(int a[], int n)
{
//排序
for (int i = n - 1;i > 0;i--)
{
swap(a[i], a[0]);    //依次交换根节点和最后一个节点
HeapAdjust(a, 0, i); //然后调整剩余的节点
}
}


(25).堆排序

/****************************************
* function          合并两个有序序列        *
* param     a[]     待合并的序列          *
* param     start   序列1的起始位置        *
* param     mid     分界线             *
* param     end     序列2的终点位置        *
* param     c[]     辅助数组
* return            无                   *
*****************************************/
void MergeSeqs(int a[],int start,int mid,int end, int c[])
{
//相当于把一个数组一分为二,a[start...mid-1],a[mid...end];
int i = start, j = mid + 1, n = mid, m = end,k = 0;
while (i <= n && j <= m)
{
if (a[i] < a[j]) c[k++] = a[i++];
else c[k++] = a[j++];
}
while (i <= n) c[k++] = a[i++];
while (j <= m) c[k++] = a[j++];
for (i = 0;i < k;i++)
a[i + start] = c[i];  //将排好序的数组重新赋值到原数组中
}

/****************************************
* function          归并排序                *
* param     a[]     待排序的序列          *
* param     start   序列的起始位置     *
* param     end     序列的终点位置     *
* param     c[]     辅助数组
* return            无                   *
*****************************************/
void Merge_Sort(int a[], int start, int end,int c[])
{
if (start < end)
{
int mid = (start + end) / 2;
Merge_Sort(a, start, mid, c);
Merge_Sort(a, mid + 1, end, c);
MergeSeqs(a, start, mid, end, c);
}
}

/****************************************
* function          归并排序                *
* param     a[]     待排序的序列          *
* param     start   序列的起始位置     *
* param     end     序列的终点位置     *
* return            无                   *
* good time         O(NlogN)            *
* avg time          O(NlogN)            *
* bad time          O(NlogN)            *
* space             O(N)                *
* stable            yes                 *
*****************************************/
void MergeSort(int a[], int n)
{
int* c = new int
;
memset(c, 0, n);
Merge_Sort(a, 0, n - 1, c);
delete[] c;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: