单链表常见面试题(一)

来源:互联网 发布:php 生成文本文件 编辑:程序博客网 时间:2024/06/03 20:10

1.比较顺序表和链表的优缺点,说说它们分别在什么场景下使用?

    顺序表:类似于数组结构。是连续存储的。在读取的时候比较快。但是在插入和删除时移动的数据量比较大,非常麻烦。顺序表在开辟空间时在满的时候重新申请开辟大的空间,会存在空间浪费。

    链表:是链式结构。对于插入和删除操作比较方便。在开辟空间时对单个节点申请空间,不会造成空间的浪费。


首先我们时间上来进行分析:

(1)对于顺序表。不论是静态的还是动态的,他们都是连续的存储空间,在读取上时间效率比较快,可以通过地址之间的运算来进行访问,但是在插入和删除操作会出现比较麻烦的负载操作。 
(2)对于链表,因为他是链式存储。在我们需要的时候才在堆上开辟空间,对于插入查找的方式比较便携。但是对于遍历的话需要多次的空间跳转。

顺序表多用于遍历操作频繁时使用,链表多用于在删除插入多的时候使用。

2.从尾到头打印单链表 

    这个问题我们使用递归的思想来解决。从头结点开始,在打印头结点数据前先打印它前一个节点数据,以此类推,首先打印最后一个节点数据,调用结束返回上一层。

void RePrintf(Node* pList)//2逆序打印{if (pList){RePrintf(pList->next);  //递归调用printf("%d->", pList->data);}}

3.删除一个无头单链表的非尾节点 

   在这个问题上,要保证删除后前后节点依旧能够连接。但在单链表中,我们无法找到要删除的结点的前一个节点,但是我们可以找到下一个。因此交换当前要删除的结点和下一个节点的数据。再把前后链接关系写上,就可以来解决。


我们在这里本来是删除cur,这里用next替换删除。
void EraseNontail(Node**pList, Node* pos)//删除无头单链表的非尾节点{assert(pos);assert(*pList);Node* cur = pos->next;pos->data=cur->data ;pos->next = cur->next;free(cur);cur = NULL;}


4.在无头单链表的一个节点前插入一个节点 

    在插入节点时,我们也要把插入后的前后链接关系写清楚。所以依旧用替换法。本来是插入到pos节点,但我们找到pos的next节点。交换数据。



void InsertNonHead(Node**pList, Node* pos,DataType x)//4无头单链表前插入{assert(pos);assert(*pList);Node* cur = BuyNode(x);Node* next = pos->next;DataType tmp = cur->data;cur->data = pos->data;pos->data = tmp;pos->next = cur;cur->next = next;}

5.单链表实现约瑟夫环 

Node* JosephRing(Node* pList, int k)//5约瑟夫环{Node* cur = pList;Node* next = NULL;Node* tail = pList;while (tail->next){tail = tail->next;}tail->next = pList;while (cur->next != cur){int count = k;while (--count){cur = cur->next;}    next = cur->next;cur->data = next->data;cur->next = next->next;free(next);next = NULL;}return cur;}



6.逆置/反转单链表


void ReList(Node** pList)//6逆置反转单链表{if (*pList == NULL){return 0;}Node* cur = *pList;*pList = NULL;while (cur){Node* tmp = cur;cur = cur->next;PushFront(pList, tmp->data);//头插到pList中}}
7单链表排序(冒泡排序&快速排序)


设置一个tail,用来表明已经有序的界限。每次把cur和next进行比较。若next->data 大于 cur->data,则交换两个值。继续走,直到遇到tail

void SortList(Node* pList)//   7冒泡排序{if (pList == NULL || pList->next == NULL){return;}Node* tail = NULL;while (tail != pList)//总体循环条件  一共排序多少次{Node* cur = pList;Node* next = cur->next;int flag = 0;while (next != tail)    //在每一次中的循环条件  {if (cur->data > next->data){int data = cur->data;cur->data = next->data;next->data = data;flag = 1;}cur = next;next = cur->next;}tail = next;if (flag == 0)     //用来优化冒泡排序,若flag值没有变化,则表明已经有序。直接return{   return;}}}


8合并两个有序链表,合并后依然有序 

Node* Merge(Node* List1, Node* List2)//8合并两个有序单链表{if (List1 == NULL)       //如果List1为空,则返回List2{return List2;}else if(List2 == NULL)   //如果List2为空,则返回List1{return List1;}else{//找出新链表的头结点   此时List1和List2都不为空,比较两个链表第一个节点的值,新链表指向值较小的那个链表Node* head = NULL;if (List1->data > List2->data){head = List2;  List2 = List2->next;  //此时,新链表指向List2}else{head = List1;List1 = List1->next;      //此时,新链表指向List1}//尾插Node* tail = head;while (List1&&List2)  //List1和List2都不为空{if (List1->data <List2->data)   选择List1和List2中指向节点中值较小的那个节点,链接到新链表中{tail->next= List1;List1 = List1->next;}else{tail->next= List2;List2 = List2->next;}tail = tail->next;}
                //while循环出来,可能是List1为空或者List2为空。把不为空的链表直接链接到新表的尾if (List1){tail->next = List1;}else{tail->next = List2;}return head;}}

9.查找单链表的中间节点,要求只能遍历一次链表

这里我们使用快慢指针。快指针每次比慢指针多走一步。当fast走到最后时,slow当好指向是中间节点 。

当节点数为偶数个,slow指向中间后面的那个。

当节点数为奇数个,slow刚好指向最中间。

Node* FindMiddle(Node* pList)//9遍历一遍找中间数{if (pList == NULL){return NULL;}Node* slow = pList;Node* fast = pList;while (fast&&fast->next){slow = slow->next;fast = fast->next->next;}return slow;}
10.查找单链表的倒数第k个节点,要求只能遍历一次链表

求倒数第K个,同样使用快慢指针。不过让快指针先走K-1.步,此时再让快慢指针同时走。当快指针走到最后一个节点,慢指针刚好走到倒数第K个

Node* CountBackward(Node* pList, int k)//10找倒数第K个节点{Node* fast = pList;Node* slow = pList;while (--k)      //让快指针先走K-1{fast = fast->next;}while (fast&&fast->next){fast = fast->next;slow = slow->next;}return slow;}