首页 > 代码库 > 【算法导论】学习笔记——第10章 基本数据结构

【算法导论】学习笔记——第10章 基本数据结构

基本数据结构主要包括:栈、队列、链表和有根树。

10.1 栈和队列
栈和队列都是动态集合,且在其上进行DELETE操作所移除的元素时预先设定的。在栈中,被删除的是最近插入的元素:栈实现的是一种后进先出(LIFO)策略。队列实现的是一种先进先出(FIFO)策略。

栈上的INSERT操作称为压入(PUSH),无参数的DELETE操作称为弹出(POP)。栈操作的代码非常简单:

 1 typedef struct { 2     int A[MAXN]; 3     int top; 4 } Stack_st; 5  6 int StackIsEmpty(Stack_st *S) { 7     if (S->top == 0) 8         return 1; 9     return 0;10 }11 12 void Push(Stack_st *S, int x) {13     if (S->top+1 < MAXN) {14         S->top = S->top + 1;15         S->A[S->top] = x;16     }17 }18 19 int Pop(Stack_st *S) {20     if ( StackIsEmpty(S) ) {21         perror("underflow\n");22         return -1;23     } else {24         --S->top;25         return S->A[S->top+1];26     }27 }
View Code

三种栈操作的执行时间都是O(1)。
队列
队列上的INSERT操作称为入队(ENQUEUE),DELETE操作称为出队(DEQUEUE)。队列操作的代码也非常简单:

 1 typedef struct { 2     int A[MAXN]; 3     int head, tail; 4     int length; 5 } Queue_st; 6  7 int QueueIsEmpty(Queue_st *Q) { 8     if (Q->head == Q->tail) 9         return 1;10     return 0;11 }12 13 int QueueIsFull(Queue_st *Q) {14     if ((Q->tail+1)%Q->length == Q->head)15         return 1;16     return 0;17 }18 19 void Enqueue(Queue_st *Q, int x) {20     if (QueueIsFull(Q)) {21         perror("overflow\n");22         return ;23     }24     Q->A[Q->tail] = x;25     if (Q->tail == Q->length)26         Q->tail = 1;27     else28         ++Q->tail;29 }30 31 int Dequeue(Queue_st *Q) {32     int x;33 34     if (QueueIsEmpty(Q)) {35         perror("underflow\n");36         return -1;37     } else {38         x = Q->A[Q->head];39         if (Q->head == Q->length)40             Q->head = 1;41         else42             ++Q->head;43         return x;44     }45 }
View Code


10.2 链表
链表(linked list)是一种这样的数据结构,其中的各对象按线性顺序排列。与数组不同的是,链表的顺序是由各个对象里的指针决定的。链表主要包括搜索、插入和删除操作。双向链表的操作代码如下。

 1 typedef struct Node { 2     int key; 3     struct Node *pre, *next; 4 } Node; 5  6 typedef struct { 7     Node *head; 8 } List; 9 10 Node *List_Search(List L, int k) {11     Node *p = L.head;12     while (p!=NULL && p->key!=k)13         p = p->next;14     return p;15 }16 17 void List_Insert(List *L, Node *x) {18     x->next = L->head;19     if (L->head != NULL)20         L->head->pre = x;21     L->head = x;22     x->pre = NULL;23 }24 25 void List_Delete(List *L, Node *x) {26     if (x->pre != NULL)27         x->pre->next = x->next;28     else29         L->head = x->next;30     if (x->next != NULL)31         x->next->pre = x->pre;32     free(x);33 }
View Code

哨兵(sentinel)是一个哑对象,其作用是简化边界条件的处理。使用哨兵后,相关操作如下所示。

 1 typedef struct { 2     Node *nil; 3 } LList; 4  5 Node *LList_Search(LList L, int k) { 6     Node *p = L.nil->next; 7     while (p!=L.nil && p->key!=k) 8         p = p->next; 9     return p;10 }11 12 void LList_Insert(LList *L, Node *x) {13     x->next = L->nil->next;14     L->nil->next->pre = x;15     L->nil->next = x;16     x->pre = L->nil;17 }18 19 void LList_Delete(LList *L, Node *x) {20     x->pre->next = x->next;21     x->next->pre = x->pre;22     free(x);23 }
View Code

哨兵基本不能降低数据结构相关操作的渐近时间界,但可以降低常数因子。这哨兵其实就是《数据结构》里的头结点。

10.2-4 LIST_SEARCH‘过程中的每一次循环迭代都需要两个测试,一是检查x!=L.nil,另一个是检查x.key!=k试说明如何在每次迭代中省略对x!=L.nil的检查。
解:x!=L.nil主要是为了防止循环无限查找,为了省略x!=L.nil的检查,即需要在检查到尾结点可退出循环,即尾结点的下一个结点需要满足x.key==k,则进行搜索过程后,将哨兵的key值赋为k即可,代码实现如下:

1 Node *LList_Search(LList L, int k) {2     Node *p = L.nil->next;3     L.nil->key = k;4     while (p->key!=k)5         p = p->next;6     return p;7 }

10.2-6 选用合适的数据结构,支持O(1)时间的UNION操作。
解:双向循环链表(其实带不带哨兵均可,以带哨兵为例)。没什么技巧,就是把头尾结点该链的链好。

1 void LList_Union(LList *L, LList *L1, LList *L2) {2     L->nil->pre = L2->nil->pre;3     L->nil->next = L1->nil->next;4     L1->nil->pre->next = L2->nil->next;5     L2->nil->next->pre = L1->nil->pre;6     L2->nil->pre->next = L->nil;7     L1->nil->next->pre = L->nil;8 }

10.2-7


 


 

【算法导论】学习笔记——第10章 基本数据结构