最近在复习考研数据结构的算法,把自己做过的觉得有意思的算法做个总结。本文均采用C语言形式的算法,并且本文算法会以考研算法的流程走,即先写算法思想,再上代码。考研算法比较偏向于算法思想,所以代码部分可能存有伪代码元素,不一定能正确运行。但是关键是掌握这个算法思想,这个才是重中之中。
1、给定两个链表,编写算法找出两个链表的公共结点。
(好像是剑指offer的一道原题)
算法思想:两个链表中有公共结点的话,那么从第一个公共节点开始,后面的结点都是相同的,不可能在出现分叉(我的理解是链表中的val和next都相同)。又由于两个链表的长度不一定一样,那么先在较长链表上遍历k个结点,再同步遍历两个链表,保证两个链表同时达到最后一个结点,这样也就保证了能够同时到达第一个公共结点。
LinkList SearchFirst(LinkList L1,LinkList L2){
//获取两个链表长度
int len1=getLength(L1);
int len2=getLength(L2);
//用于指向较长较短链表的第一个结点
LinkList longlist,shotlist;
if(len1>len2){
longlist = L1->next;
shouList = L2->next;
int dist = len1 - len2;
}else{
longlist = L2->next;
shouList = L1->next;
int dist = len2 - len1;
}
while(dist--)
longlist = longlist->next;
while(longlist!=null){
if(longlist == shoulist)
return longlist;
else{
longlist = longlist->next;
shoutlist = shouList->next;
}
}
//没有公共结点
return null;
}
2、链表返回倒数第k个数的位置的值
算法思想一:先遍历链表得出链表总产长度,然后返回链表第n-k+1个节点的数据域
int searchK1(LinkList L,int k){
int i = 0,j = 0;
LNode *p = L->next;
while(p!=null){
i++;
p = p->next;
}
p = p->next;
while(j<i-k){
j++;
p = p->next;
}
return p->data;
}
算法思想二:定义两个指针p,q,初始时两个都指向链表的第一个个元素结点,首先让p沿链表移动k个节点,然后再让p,q两个指针同时沿链表遍历结点。当p指针移动到最后一个结点时,q所指向的结点即为倒数第k个结点。
int searchk2(LinkList L,int k){
LNode *p = L->next,*q = L->next;
int count = 0;
while(p!=null){
if(count<k)
count ++;
else
q = q->next;
p = p->next;
}
if(count<k){//表长小于k,查找失败
exit(0);
}
else{
return q->data;
}
}
3、将两个递增单链表合并成一个非递增单链表
算法思想:两个链表已经按元素之递增次序排序,将其合并时,均从第一个结点起进行比较,将小的结点链入链表中,同时后移工作指针。该问题要求结果链表按元素值递减排序,故采用头插法。比较结束后,可能会有一个链表非空,此时用头插法将剩余链表插入新链表中。
void Merge(LinkList *La,LinkList *Lb){
LinkList *r,
*pa = La->next;
*pb = Lb->next;
La->next = null;//La作为结果链表的头指针,先将结果链表初始化为空
while(pa && pb){//两链表都不为空时循环
if(pa->data <= p->data){
r = pa->next;//r暂存pa的后继结点指针,以免丢失
pa->next = La->next;//头插法核心代码一
La->next = pa;//头插法核心代码二
pa = r;
}else{
r = pb->next;//r暂存pa的后继结点指针,以免丢失
pb->next = La->next;//头插法核心代码一
La->next = pb;//头插法核心代码二
pb = r;
}
}
if(pa)
pb = pa;//通常情况下会剩余一个链表非空,处理剩余部分
while(pb){
r = pb->next;//r暂存pa的后继结点指针,以免丢失
pb->next = La->next;//头插法核心代码一
La->next = pb;//头插法核心代码二
pb = r;
}
free(Lb);
}
4、将带头结点的单链表进行逆置
算法思想:逆置链表初始化为空,依次从原链表中删除结点,将删除的节点依次插入到逆置链表中(用头插法插入),如此循环直至原链表为空
void reverse(LinkList *head){
LinkList *r,
*p = head->next;//指针p指向原链表的第一个结点
head->next = null;//这一步是初始化逆置链表操作
while(p){
r = p->next;//r暂存p后面的结点,以免丢失
p->next = head->next;//头插法核心代码一
head->next = p;//头插入核心代码二
p = r;
}
}
小结:通过3和4可以得出,要将一个链表的值从后面排到前面,可以用头插法实现其链表的逆置。
5、有两个有序链表,如何以最优的方式找出二者的交集,并且把它存在第一链表中
算法思想:采用归并的思想,设置两个工作指针pa,pb,对两个链表进行归并扫描,只有同时出现在两个集合中的元素才链接到结果链表中,结果只保留一个,其他节点全部释放。当一个链表遍历完毕之后,释放另一个链表的全部结点。
LinkList Union(LinkList *La,LinkList *Lb){
LinkList *pa = La->next,
*pb = Lb->next,
*pc = La;
LNode *q;
while(pa && pb){
if(pa->data == pb->data){
pc->next = pa;
pc = pa;
pa = pa->next;
q = pb;
pb = pb->next;
free(q);
}
else if(pa->data<pb->data){
q = pa;
pa = pa->next;
free(q);
}
else{
q = pb;
pb = pb->next;
free(q);
}
}
//while循环结束
while(pa){//Lb遍历完,La还未结束
q = pa;
pa = pa->next;
free(q);
}
while(pb){//La遍历完,Lb还未结束
q = pb;
pb = pb->next;
free(q);
}
pc->next = null;
free(Lb);
return La;
}
6、两个整数递增有序序列A、B分别有n,m个元素,求第k大的数,要求最佳时间复杂度
算法思想:符合归并排序的思想,直接将A、B进行归并排序,然后在新的数组里面寻找第k大的元素,时间复杂度为(max(m,n))
int Merge(int a[],int b[],int k){
int i=0,j=0,m=o;
while(i<a.length && j<b.length){
if(a[i]<=b[j])
c[m++]=a[i++];
else
c[m++]=b[j++];
}
while(i<a.length)
c[m++]=a[i++];
while(j<b.length)
c[m++]=b[j++];
return c[k-1];
}
7、设有一个线性表,存放在一维数组a[0…n-1]中,编程将数组中的每一个元素循环右移k位,要求只用一个辅助单元,时间复杂度为O(n)
算法思想:
先把0–n-k-1的部分置逆。再把n-k–n-1部分置逆。最后再把数组0—n-1置逆。
验证算法:
a(0)–a(n-k-1)移动到最终位置a(k)–a(n-1). a(n-k)–(n-1)移动到最终位置为a(0)—a(k-1).
a(0)–a(n-k-1)置逆以后为a(n-k-1) – a(0). 再把数组a(n-k)–a(n-1)置逆时,
a(n-k-1) – a(0)后面变为a(n-1)—a(n-k). 整个数组在置逆,变成a(n-k)----a(n-1) a(0)----a(n-k-1)。所以上面算法正确。
算法实现:
1、实现设置两个指针,分别设置为置逆部分的头尾部分,向中间移动,直到相等。移动一次就交换头尾指针的元素。
2、第一次0–n-k-1的部分置逆。再把n-k–n-1部分置逆。最后再把数组0—n-1置逆。辅助空间为O(1).时间复杂度为O(n)
//反转数组部分,也就是逆置
void Reverse(int R[],int l,intr)
{
int i,j;
int temp;
for(i=l,j=r;i<j;++i,--j)
{
temp=R[i];
R[i]=R[j];
R[j]=temp;
}
}
void RCR(int a[],int n,int k)
{
if(k<=0||k>=n)
cout<<"ERROR"<<endl;
else
{
Reverse(R,0,n-k-1);
Reverse(R,n-k,n-1);
Reverse(R,0,n-1);
}
}
总结:此题略有难度,需要画图配合着理解
8、已知线性表(a1,a2,…an)按顺序存于内存中,每个元素都是整数,试设计用最少的时间把所有负数的元素都移到正数前面的算法
算法思想:要求重排n个元素且以顺序存储的结构存储线性表,使得所有负数元素全部在正数前面,这不就符合快速排序的思想吗?直接采用快速排序来实现,值得注意的是,算法中提出暂存的第一个元素(枢轴)并作为排序比较的标准,比较的标准以元素是否为负数
int ReArrange(SeqList a,int n){
int low = 0,high = n-1;
int t = a[low];//枢轴元素,只是暂存,不作为比较对象
while(low<high){
while(low<high && a[high]>=0)
high--;
a[low] = a[high];
while(low<high && a[low]<0)
low++;
a[high] = a[low];
}
a[low]=t;//将枢轴元素放到最终位置
}