1.(常见问题,对于面试公司产品的特点是否了解)趋势科技产品特点:
1.占用资源较小;2.防毒能力强;3.可以在文件下载过程中就检测出是否感染了病毒(包括rar,zip中的文件);4.扫描速度超快。
2.冒泡排序,快速排序,堆排序的原理,应用场景和时间复杂度
#1.时间复杂度:“快些以nlog(2)n的速度归队。”其中,“快”指快速排序,“些”指希尔排序,“归”归并排序,“队”堆排序。
#2.空间复杂度:快速排序为O(nlog(2)n),归并排序为O(n),基数排序为O(rd),其他都是O(1)。
#3.算法稳定性总结:“心情不稳定,快些选一堆好友来聊天吧”。
“快”是快速排序,“些”是希尔排序,“选”是指简单选择排序,“堆”是堆排序。这4中是不稳定的,其他自然都是稳定的。
#4.其他细节(与排序原理相关)
1)经过一趟排序,能够保证一个元素到达最终位置,这样的排序是交换类的那两种(冒泡,快速)和选择类的两种(简单选择,堆)。
2)排序方法的元素比较次数和原始序列无关——简单选择排序和折半插入排序。
#5.一个非常有用的结论:借助于“比较”进行排序的算法,在最坏情况下能达到的最好时间复杂度为O(nlog2n)。
3.冒泡排序法
代码如下:
void BubbleSort(int x[],int n){
int i,j;
int k;
for(i=0;i<n;i++)
{
for(j=0;j<n-i-1;j++)
{
k=x[j];
x[j]=x[j+1];
x[j+1]=k;
}
}
}
4.快速排序的推导和代码实现
快速排序采用了一种分治的策略,通常称其为分治法。分治法的基本思想:将原问题分解为若干个规模更小但结构与原问题相似的子问题。递归地解这些子问题,然后将这些子问题的解组合为原问题的解。
快速排序的基本思想:设当前待排序的无序区为A[low…high],利用分治可以描述为:
(1)分解:在A[low…high]中任选一个记录作为基准(pivot),以此基准将当前无序区划分为左右两个较小的子区间A[low…pivotpos-1]和A[pivotpos+1…high],并使左边子区间中所有记录的关键字均小于等于基准记录(pivot),右边的子区间中所有记录的关键字均大于等于pivot,而基准记录pivot则位于正确的位置上,它无需参加后序的排序。
注意:划分的关键是要求出基准记录所在的位置pivotpos。划分的结果可以简单地表示为(pivot=A[pivotpos]):A[low…pivotpos-1]<=A[pivotpos]<=A[pivotpos+1…high],其中low<=pivotpos<=high。
(2)求解:通过递归调用快速排序对左,右子区间A[low…pivotpos-1]和A[pivotpos+1…high]快速排序。
(3)组合:当“求解”步骤中的两个递归调用结束时,其左右两个子区间已经有序。对快速排序而言,组合步骤无须做什么,可看作空操作。
代码如下:
void quick_sort(int a[],int low,int high)
{
int i,j,pivot;
if(low<high)
{
pivot=a[low];
i=low;
j=high;
while(i<j)
{
while(i<j && a[j]>=pivot)
j--;
if(i<j)
a[i++]=a[j]; //将比pivot小的元素移到低端
while(i<j && a[i]<=pivot)
i++;
if(i<j)
a[j--]=a[i]; //将比pivot大的元素移到高端
}
a[i]=pivot; //pivot移到最终位置
quick_sort(a,low,i-1);//对左区间递归顺序
quick_sort(a,i+1,high);//对右区间递归排序
}
}
这里pivot代表基准值,它的初始值为a[low]。局部变量i和j分别代表low和high的位置。接着按照下面的步骤进行一趟交换。
(1)把比pivot小的元素移到低端(low)。
(2)把比pivot大的元素移到高端(high)。
(3)pivot移到最终位置,此时这个位置的左边元素的值都比pivot小,而其右边元素的值都比pivot大。
(4)对左,右区间分别进行递归排序。从而把前三步的粗排序逐渐细化,直至最终low和high交汇。
5.堆排序的推导和代码实现
堆排序:
堆排序定义:n个序列A1,A2,…An称为堆,有以下两种不同类型的堆。
小根堆:所有子结点都大于其父节点,即Ai<=A2i且Ai<=A2i+1。
大根堆:所有子结点都小于其父节点,即Ai>=A2i且Ai>=A2i+1。若将此序列所存储的向量A[1…n]看为一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:树中任一非叶子结点的关键字均不大于(或不小于)其左,右子结点(若存在)的关键字。
因此堆排序(heapSort)是树形选择排序。在排序过程中,将R[1…n]看成一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序区中选择关键字最大(或最小)的记录。
堆排序过程描述如下:
1)从无序序列所确定的完全二叉树的第一个非叶子结点开始,从右至左,从上至下,对每个结点进行调整,最终将得到一个大顶堆。
对结点的调整方法:
将当前结点(假设为a)的值与其孩子结点进行比较,如果存在大于a值的孩子结点,则从中选出最大的一个与a交换。当a来到下一层的时候重复上述过程,知道a的孩子结点值都小于a的值为止。
2)将当前无序序列中第一个元素,反映在树中是根结点(假设为a)与无序序列中最后一个元素交换(假设为b)。a进入有序序列,到达最终位置。无序序列中元素减少1个,有序序列中元素增加1个。此时只有结点b可能不满足堆定义,对其进行调整。
3)重复2)中过程,直到无序序列中的元素剩下1个时排序结束。
代码如下:
void Sift(int R[],int low,int high)//因R[]中是一棵完全二叉树,所以元素的存储必须从1开始
{
int i=low,j=2*i; //R[j]是R[i]的左孩子结点
int temp=R[i];
while(j<=high)
{
if(j<high&&R[j]<R[j+1]) //若右孩子较大,则把j指向右孩子
++j; //j变为2*i+1
if(temp<R[j])
{
R[i]=R[j]; //将R[j]调整到双亲结点的位置上
i=j; //修改i和j的值,以便继续向下调整
j=2*i;
}
else
break; //调整结束
}
R[i]=temp; //被调整结点的值放入最终位置
}
/*堆排序函数*/
void heapSort(int R[],int n)
{
int i;
int temp;
for(i=n/2;i>=1;--i) //建立初始堆
Sift(R,i,n);
for(i=n;i>=2;--i) //进行n-1次循环完成堆排序
{
/*以下三句换出了根结点中元素,将其放入最终位置*/
temp=R[1];
R[1]=R[i];
R[i]=temp;
Sift(R,1,i-1); //在减少了1个元素的无序序列中进行调整
}
}
【用大根堆排序的基本思想:
(1)先将初始A[1…n]建成一个大根堆,此堆为初始的无序区。
(2)再将关键字最大的记录A[1](堆顶)和无序区的最后一个记录A[n]交换,由此得到新的无序区A[1…n-1]和有序区A[n],且满足A[1…n-1]<=A[n]。
(3)由于交换后新的根A[1]可能违反堆性质,故应将当前无序区A[1…n-1]调整为堆。然后再次将A[1…n-1]中关键字最大的记录A[1]和该区间的最后一个记录A[n-1]交换,由此得到新的无序区A[1…n-2]和有序区A[n-1…n],且仍满足关系A[1…n-2]<=A[n-1…n],同样要将A[1…n-2]调整为堆。
(4)对调整的堆重复进行上面的交换,直到无序区只有一个元素为止。
构造初始堆必须用到调整堆的操作,现在说明heapify函数思想方法。
每趟排序开始前,A[1…i]是以A[1]为根的堆,在A[1]与A[i]交换后,新的无序区A[1…i-1]中只有A[1]的值发生了变化,故除A[1]可能违反堆性质外,其余任何结点为根的子树均是堆。因此,当被调整区间是A[low….high]时,只需调整以A[low]为根的树即可。
可以使用“刷选法”进行堆的调整。A[low]的左,右子树(若存在)均已是堆,这两棵子树的根A[2low]和A[2low++]分别是各自子树中关键字最大的结点。若A[low]不小于这两个孩子节点的关键字,则A[low]未违反堆性质,以A[low]为根的树已是堆,无序调整;否则必须将A[low]和它的两个孩子节点中关键字较大者进行交换,即A[low]与A[large](A[large]=max(A[2low],A[2low+1])交换。交换后又可能使节点A[large]违反堆性质。同样,由于该节点的两棵树(若存在)仍然是堆,故可重复上述调整过程,对以A[large]为根的树进行调整。此过程直至当前被调整的节点已满足堆性质,或者该节点已是叶子为止。上述过程就像过涮子一样,把较小的关键字逐层涮下去,而把较大的关键字逐层涮上来。】