数据结构面试大全

(zz)
2007-09-28 00:49
1.判断链表是否存在环型链表 问题:判断一个链表是否存在环,例如下面这个链表就存在一个环:
例如 N1->N2->N3->N4->N5->N2 就是一个有环的链表,环的开始结点是 N5 这里有一个比较简单的解法。设置两个指针 p1 p2 。每次循环 p1 向前走一步, p2 向前走两步。直到 p2 碰到 NULL 指针或者两个指针相等结束循环。如果两个指针相等则说明存在环。
struct link 
{
  int data;
   link* next;
};
 
bool IsLoop(link* head)
{
   link* p1=head, *p2 = head;
   if (head ==NULL || head->next ==NULL) 
   {
      return false;
   }
   do{
   p1= p1->next;
   p2 = p2->next->next;
   } while(p2 && p2->next && p1!=p2);   
   if(p1 == p2)
      return true;
   else
      return false;
}
2, 链表反转 单向链表的反转是一个经常被问到的一个面试题,也是一个非常基础的问题。比如一个链表是这样的: 1->2->3->4->5 通过反转后成为 5->4->3->2->1 。最容易想到的方法遍历一遍链表,利用一个辅助指针,存储遍历过程中当前指针指向的下一个元素,然后将当前节点元素的指针反转后,利用已经存储的指针往后面继续遍历。源代码如下:
struct linka {
   int data;
   linka* next;
};
 
void reverse(linka*& head)
{
   if(head ==NULL)
   return;
   linka*pre, *cur, *ne;
   pre=head;
   cur=head->next;
   while(cur)
   {
   ne = cur->next;
   cur->next = pre;
   pre = cur;
   cur = ne;
   }
   head->next = NULL;
   head = pre;
}
还有一种利用递归的方法。这种方法的基本思想是在反转当前节点之前先调用递归函数反转后续节点。源代码如下。不过这个方法有一个缺点,就是在反转后的最后一个结点会形成一个环,所以必须将函数的返回的节点的 next 域置为 NULL 。因为要改变 head 指针,所以我用了引用。算法的源代码如下:
linka* reverse(linka* p,linka*& head)
{
   if(p == NULL || p->next == NULL)
   {
   head=p;
   return p;
   }
   else
   {
   linka* tmp = reverse(p->next,head);
   tmp->next = p;
   return p;
   }
}
3, 判断两个数组中是否存在相同的数字 给定两个排好序的数组,怎样高效得判断这两个数组中存在相同的数字?
这个问题首先想到的是一个 O(nlogn) 的算法。就是任意挑选一个数组,遍历这个数组的所有元素,遍历过程中,在另一个数组中对第一个数组中的每个元素进行 binary search 。用 C++ 实现代码如下:
bool findcommon(int a[],int size1,int b[],int size2)
{
   int i;
   for(i=0;i<size1;i++)
   {
   int start=0,end=size2-1,mid;
   while(start<=end)
   {
   mid=(start+end)/2;
   if(a[i]==b[mid])
   return true;
   else if (a[i]<b[mid])
   end=mid-1;
   else
   start=mid+1;
   }
   }
   return false;
}
后来发现有一个 O(n) 算法。因为两个数组都是排好序的。所以只要一次遍历就行了。首先设两个下标,分别初始化为两个数组的起始地址,依次向前推进 。推进的规则是比较两个 数组中的数字,小的那个数组的下标向前推进一步,直到任何一个数组的下标到达数组末尾时,如果这时还没碰到相同的数字,说明数组中没有相同的数字。
bool findcommon2(int a[], int size1, int b[], int size2)
{
   int i=0,j=0;
   while(i<size1 && j<size2)
   {
   if(a[i]==b[j])
     return true;
   if(a[i]>b[j])
   j++;
   if(a[i]<b[j])
   i++;
   }
   return false;
}
4, 最大子序列 问题:
给定一整数序列 A1 A2 ... An (可能有负数),求 A1~An 的一个子序列 Ai~Aj ,使得 Ai Aj 的和最大
例如:
整数序列 -2, 11, -4, 13, -5, 2, -5, -3, 12, -9 的最大子序列的和为 21
对于这个问题,最简单也是最容易想到的那就是穷举所有子序列的方法。利用三重循环,依次求出所有子序列的和然后取最大的那个。当然算法复杂度会达到 O(n^3) 。显然这种方法不是最优的,下面给出一个算法复杂度为 O(n) 的线性算法实现,算法的来源于 Programming Pearls 一书。 在给出线性算法之前,先来看一个对穷举算法进行优化的算法,它的算法复杂度为 O(n^2) 。其实这个算法只是对对穷举算法稍微做了一些修改:其实子序列的和我们并不需要每次都重新计算一遍。假设 Sum(i, j) A[i] ... A[j] 的和,那么 Sum(i, j+1) = Sum(i, j) + A[j+1] 。利用这一个递推,我们就可以得到下面这个算法:
int max_sub(int a[],int size)
{
   int i,j,v,max=a[0];
   for(i=0;i<size;i++)
   {
   v=0;
   for(j=i;j<size;j++)
   {
   v=v+a[j];//Sum(i, j+1) = Sum(i, j) + A[j+1]
   if(v>max)
   max=v;
   }
   }
   return max;
}
那怎样才能达到线性复杂度呢?这里运用动态规划的思想。先看一下源代码实现:
int max_sub2(int a[], int size)
{
   int i,max=0,temp_sum=0;
   for(i=0;i<size;i++)
   {
   temp_sum+=a[i];
   if(temp_sum>max)
   max=temp_sum;
   else if(temp_sum<0)
   temp_sum=0;
   }
   return max;
}
在这一遍扫描数组当中,从左到右记录当前子序列的和 temp_sum ,若这个和不断增加,那么最大子序列的和 max 也不断增加 ( 不断更新 max) 。如果往前扫描中遇到负数,那么当前子序列的和将会减小。此时 temp_sum 将会小于 max ,当然 max 也就不更新。如果 temp_sum 降到 0 时,说明前面已经扫描的那一段就可以抛弃了,这时将 temp_sum 置为 0 。然后, temp_sum 将从后面开始将这个子段进行分析,若有比当前 max 大的子段,继续更新 max 。这样一趟扫描结果也就出来了。
5, 找出单向链表的中间结点 这道题和解 判断链表是否存在环 ,我用的是非常类似的方法,只不过结束循环的条件和函数返回值不一样罢了。设置两个指针 p1 p2 。每次循环 p1 向前走一步, p2 向前走两步。当 p2 到达链表的末尾时, p1 指向的时链表的中间。
link* mid(link* head)
{
   link* p1,*p2;
   p1=p2=head;
   if(head==NULL || head->next==NULL)
      return head;
   do {
      p1=p1->next;
      p2=p2->next->next;
   } while(p2 && p2->next);
   return p1;
}

你可能感兴趣的:(数据结构)