1. 怎样判断一个链表有环
(1)最常用方法
定义两个指针,同时从链表的头节点出发,一个指针一次走一步,另一个指针一次走两步。如果走得快的指针追上了走得慢的指针,那么链表就是环形链表;如果走得快的指针走到了链表的末尾(next指向 NULL)都没有追上第一个指针,那么链表就不是环形链表。
bool IsLoop(NODE *head) {// 假设为带头节点的单链表
if (head == NULL)
return false;
node *slow = head->next; // 初始时,慢指针从头节点开始走1步
if (slow == NULL)
return false;
node *fast = slow->next; // 初始时,快指针从头节点开始走2步
while (fast!=NULL && slow!=NULL){ //当单链表没有环时,循环到链表尾结束
if (fast == slow)
return true;
slow = slow->next; // 慢指针每次走一步
fast = fast->next;
if (fast != NULL)
fast = fast->next;
}
return false;
}
(2)通过使用STL库中的map表进行映射
首先定义 map<NODE *, int> m; 将一个 NODE * 指针映射成数组的下标,并赋值为一个 int 类型的数值。然后从链表的头指针开始往后遍历,每次遇到一个指针p,就判断 m[p] 是否为0。如果为0,则将m[p]赋值为1,表示该节点第一次访问;而如果m[p]的值为1,则说明这个节点已经被访问过一次了,于是就形成了环。
map<NODE *, int> m;
bool IsLoop_2(NODE *head){
if (head == NULL)
return false;
NODE *p = head;
while (p) {
if (m[p] == 0) // 一般默认值都是0
m[p] = 1;
else if (m[p] == 1)
return true;
p = p->next;
}
return false;
}
2. 若单链表有环,如何找出环的入口节点?
(1)定义两个指针p1和p2,在初始化时都指向链表的头节点。
(2)如果链表中的环有n个节点,指针p1先在链表上向前移动n步。
(3)然后指针p1和p2以相同的速度在链表上向前移动直到它们相遇。
(4)它们相遇的节点就是环的入口节点。
// 1、先求出环中的任一节点(slow)
NODE *MeetingNode(NODE *head) {
if (head == NULL)
return NULL;
node *slow = head->next; // 初始时,慢指针从头节点开始走1步
if (slow == NULL)
return NULL;
node *fast = slow->next; // 初始时,快指针从头节点开始走2步
while (fast != NULL && slow != NULL) {
if (fast == slow)
return fast;
slow = slow->next; // 慢指针每次走一步
fast = fast->next;
if (fast != NULL)
fast = fast->next;
}
return NULL;
}
// 2、从已找到的那个环中节点出发,一边继续向前移动,一边计数,当再次回到这个节点时,就可得到环中的节点数了。
NODE *EntryNodeOfLoop(NODE *head){
NODE *meetingNode = MeetingNode(head); // 先找出环中的任一节点
if (meetingNode == NULL)
return NULL;
int count = 1; // 计算环中的节点数
node *p = meetingNode;
while (p != meetingNode) {
p = p->next;
++count;
}
// p和q以相同的速度向前移动,当q指向环的入口节点时,p已经围绕着环走了一圈又回到了入口节点。
p = head;
for (int i = 0; i < count; i++)
p = p->next;
node *q = head; // q从头节点开始
while (q != p) {
q = q->next;
p = p->next;
}
return p;
}
3. 两个有序的单链表如何合并成一个有序的单链表
(1)方法1(局部引用)
这种方法避免使用虚拟节点(dummy node),而是使用一个指向指针的指针,struct node** lastPtrRef,这个指针指向结果链表的最后一个节点。在这个方法中,所有由虚拟节点完成的工作都有lastPtrRef完成。
struct node* SortedMerge(struct node* a, struct node* b) {
struct node* result = NULL;
/*point to the last result pointer */
struct node** lastPtrRef = &result;
while(1) {
if(a == NULL) {
*lastPtrRef = b;
break;
}
else if(b == NULL) {
*lastPtrRef = a;
break;
}
if(a->data <= b->data) {
MoveNode(lastPtrRef, &a);
}
else {
MoveNode(lastPtrRef, &b);
}
/*tricky:advance to point to the next ".next" field */
lastPtrRef = &((*lastPtrRef)->next);
}
return (result);
}
(2)方法2(递归)
合并操作是非常适合用递归来完成的一类操作,递归实现将会比迭代实现更加清晰且易于理解。尽管如此,你可能也不愿意使用递归来实现这个操作,因为递归方法所使用的栈空间与链表的长度成正比。
struct node* SortedMerge(struct node* a, struct node* b) {
struct node* result = NULL;
/*Base cases*/
if(a == NULL)
return (b);
else if(b == NULL)
return (a);
/*Pick either a or b, and recur */
if(a->data <= b->data) {
result = a;
result->next = SortedMerge(a->next, b);
}
else {
result = b;
result->next = SortedMerge(a, b->next);
}
return (result);
}
4. 双向链表排序用什么排序算法比较好
归并排序
5.求一个单链表的中间节点,要求安全检查。
//查找单链表的中间节点,要求只能遍历一次链表
SListNode * FindMidNode(SListNode * phead) {
SListNode *fast = phead;
SListNode *slow = phead;
while (fast) {
if (fast->next != NULL)
fast = fast->next->next;
else
break;
slow = slow->next;
}
return slow;
}