算法导论10-2
读书笔记
本小节介绍了链表,链表由一个个节点连接而成,单个节点的组成如下:
public class Node {
public Node previous;
public Object key;
public Node next;
}
由上述节点组成的链表称为双向链表;也有节点只有一个前驱节点或者一个后继节点,这样的链表被称为单向链表;
链表的增删查改并没有太多出奇的地方,不再赘述。
课后习题
10.2-1
单链表上的动态集合操作\(INSERT\)能否在\(O(1)\)时间内实现?\(DELETE\)操作呢?
插入操作可以;
但是删除操作需要先找到该元素的前驱节点,这需要\(O(n)\)的时间;
10.2-2
用一个单链表\(L\)实现一个栈。要求操作\(PUSH\)和\(POP\)的运行时间仍为\(O(1)\)。
入栈操作,将指定元素插入链表头部;
出栈操作,返回链表头部元素,并且删除该元素;
10.2-3
用一个单链表\(L\)实现一个队列。要求操作\(ENQUEUE\)和\(DEQUEUE\)的运行时间仍为\(O(1)\)。
入队操作,将指定元素插入链表尾部;
出队操作,返回链表头部元素,并且删除该元素;
10.2-4
如前所述,\(LIST-SEACH'\)过程中每一次循环迭代都需要两个测试:一是检查\(x \ne {L.nil}\),另一个是检查\(x.key \ne k\)。试说明如何在每次迭代中省略对\(x \ne L.nil\)的检查。
对\(L.nil\)进行检查是为了防止对链表进行再次遍历,可以让最后一个节点的后继节点指向空;
10.2-5
使用单项循环链表实现字典操作\(INSERT\)、\(DELETE\)和\(SEARCH\),并给出所写过程的运行时间。
插入操作,设插入元素\(x\),尾节点的后继节点指向\(x\),\(x\)的后继节点指向原来的头节点;时间复杂度\(O(1)\)。
搜索操作,从头节点开始搜索;时间复杂度为\(O(n)\)。
删除操作,设删除元素\(x\),先找到\(x\)的前驱节点然后修改其后继节点的引用;时间复杂度为\(O(n)\)。
10.2-6
动态集合操作\(UNION\)以两个不相交的集合\(S_1\)和\(S_2\)作为输入,并返回集合\(S=S_1 \cap S_2\),包含\(S_1\)和\(S_2\)的所有元素。该操作通常会播怀集合\(S_1\)和\(S_2\)。试说明如何选用一种合适的表类数据结构,来支持\(O(1)\)时间的\(UNION\)操作。
操作过程和插入差不多,不再赘述。
10.2-7
给出一个\(\theta(n)\)时间的非递归过程,实现对一个含\(n\)个元素的单链表的逆转。要求除存储链表本身所需空间外,该过程只能使用固定大小的存储空间。
对于单链表反转,建议阅读:
上面两篇文章,掘金的文章对递归反转讲解的不太好,代码有一些错误。
#include<iostream>
using namespace std;
//定义一个链表节点
struct ListNode
{
int value;
ListNode *next;
};
//插入一个新节点到链表中(放在链表头部)
void CreateList(ListNode * & head, int data)
{
//创建新节点
ListNode * p = (ListNode*)malloc(sizeof(ListNode));
p->value = data;
p->next = NULL;
if (head == NULL)
{
head = p;
return;
}
p->next = head;
head = p;
}
void printList(ListNode* head)
{
ListNode * p = head;
while (p != NULL)
{
cout << p->value<< " ";
p = p->next;
}
cout << endl;
}
//递归方式:实现单链表反转
ListNode * ReverseList(ListNode * head)
{
//递归终止条件:找到链表最后一个结点
if (head == NULL || head->next == NULL)
return head;
else
{
ListNode * newhead = ReverseList(head->next);//先反转后面的链表,从最后面的两个结点开始反转,依次向前
head->next->next = head;//将后一个链表结点指向前一个结点
head->next = NULL;//将原链表中前一个结点指向后一个结点的指向关系断开
return newhead;
}
}
//非递归方式:实现单链表反转
ListNode* reverseList2(ListNode* head) {
if (head == NULL || head->next == NULL)
return head;
ListNode* prev = head;
ListNode* cur = head->next;
ListNode* temp = head->next->next;
while (cur){
temp = cur->next; //temp作为中间节点,记录当前结点的下一个节点的位置
cur->next = prev; //当前结点指向前一个节点
prev = cur; //指针后移
cur = temp; //指针后移,处理下一个节点
}
head->next = NULL; //while结束后,将翻转后的最后一个节点(即翻转前的第一个结点head)的链域置为NULL
return prev;
}
int main()
{
ListNode * head = NULL;
for (int i = 0; i<9; i++)
CreateList(head, i);
printList(head);
head = ReverseList(head);
printList(head);
system("pause");
return 0;
}