前言 表、栈和队列是三种最基本、最简单的数据结构,此后的很多算法与数据结构都是要基于这三种结构。其实从定义来说栈和队列也是表,只不过它们是操作受限的表,为了以防后续混淆先声明一下位序和索引的不同,位序是从 1 开始的,而索引是从 0 开始的,在后续的实现中一般的操作都是基于索引的,如果是基于位序的会在注释中标明。
表(List) 表是一个可以存储多个元素的线性数据结构,通常可以通过索引访问。 基础操作:
插入:在特定位置添加元素。
删除:从特定位置移除元素。
查找:通过索引或值查找元素。
遍历:访问表中的每个元素。
根据其物理结构(存储结构)的不同,分为:
顺序表(Sequence List) 顺序表的存储是在一片连续的空间中的,在程序中使用数组来实现,由于数组是静态的,在程序像操作系统申请分配后其大小就无法改变了,所以要实现一个扩容的动态顺序表就要在扩容申请另一片空间并将原来的数据拷贝到新的空间中,然后释放原来的空间。
以下是一个动态可扩容的顺序表的代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 #include <cstdlib> #define E int #define DEFAULT_CAPACITY 10 typedef struct SqList { int length; int capacity; E *data; } SqList; bool initList (SqList &list, int initCapacity) { if (initCapacity <= 0 ) return false ; list.length = 0 ; list.capacity = initCapacity; E *p = (E *) malloc (initCapacity * sizeof (E)); if (p == nullptr ) return false ; list.data = p; return true ; } bool initList (SqList &list) { return initList (list,DEFAULT_CAPACITY); } bool empty (SqList &list) { return list.length == 0 ; } bool increaseList (SqList &list, int newCapacity) { if (newCapacity <= list.capacity)return false ; E *oldData = list.data; E *p = (E *) malloc (newCapacity * sizeof (E)); if (p == nullptr ) return false ; list.data = p; for (int i = 0 ; i < list.length; ++i) { list.data[i] = oldData[i]; } list.capacity = newCapacity; free (oldData); return true ; } bool listInsert (SqList &list, int index,E e) { if (index <= 0 || index > list.length + 1 ) return false ; for (int i = list.length; i >= index; --i) { list.data[i] = list.data[i - 1 ]; } list.data[index - 1 ] = e; list.length++; return true ; } bool listDelete (SqList &list, int index,E &e) { if (index <= 0 || index > list.length) return false ; e = list.data[index - 1 ]; for (int i = index - 1 ; i < list.length - 1 ; ++i) { list.data[i] = list.data[i + 1 ]; } list.length--; return true ; } bool getIndexElem (SqList &list, int index, E &e) { if (index <= 0 || index > list.length) return false ; e = list.data[index - 1 ]; return true ; } bool getElemIndex (SqList &list,E e, int &index) { for (int i = 0 ; i < list.length; ++i) { if (e == list.data[i]) { index = i + 1 ; return true ; } } return false ; } void clear (SqList &list) { list.length = 0 ; }
链表(Linked List) 链表是动态可扩容的,每次添加新元素时,都会创建一个新的节点然后将尾节点连接连接到新节点上。 这个单链表的实现是包含头节点的,由于这个链表结构只包含头节点的信息,所以许多操作都较于繁琐,可以根据需求进行改造。
以下是单链表的代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 #include <cstdlib> #define E int typedef struct node { E data; node *next; } LNode, *LinkList; bool initList (LinkList &list) { LinkList p = (LinkList) malloc (sizeof (LNode)); if (p == nullptr ) return false ; list = p; list->next = nullptr ; return true ; } bool empty (LinkList &list) { return list->next == nullptr ; } bool insertNextNode (LNode *p,E e) { if (p == nullptr ) return false ; LNode *q = (LNode *) malloc (sizeof (LNode)); if (q == nullptr ) return false ; q->data = e; q->next = p->next; p->next = q; return true ; } bool insertPriorNode (LNode *p,E e) { if (p == nullptr ) return false ; LNode *q = (LNode *) malloc (sizeof (LNode)); if (q == nullptr ) return false ; q->next = p->next; p->next = q; q->data = p->data; p->data = e; return true ; } LNode *getIndexNode (LinkList list, int index) { int i = 0 ; LNode *p = list; while (p != nullptr && i < index) { p = p->next; i++; } return p; } LNode *getElemNode (LinkList list,E e) { LNode *p = list; while (p != nullptr && p->data != e) { p = p->next; } return p; } int length (LinkList list) { LNode *p = list->next; int len = 0 ; while (p != nullptr ) { p = p->next; len++; } return len; } bool listInsert (LinkList &list, int index,E e) { if (index <= 0 ) return false ; LNode *p = getIndexNode (list, index - 1 ); return insertNextNode (p, e); } bool deleteNode (LNode *p) { if (p == nullptr ) return false ; LNode *q = p->next; if (q == nullptr ) return false ; p->data = q->data; p->next = q->next; free (q); return false ; } bool listDelete (LinkList &list, int index,E &e) { if (index <= 0 ) return false ; LNode *p = getIndexNode (list, index - 1 ); if (p == nullptr ) return false ; LNode *q = p->next; if (q == nullptr ) return false ; e = q->data; p->next = q->next; free (q); return true ; } bool listInsertTail (LinkList &list,E es[], int len) { LNode *p = list; while (p->next != nullptr ) { p = p->next; } for (int i = 0 ; i < len; ++i) { if (!insertNextNode (p, es[i])) return false ; p = p->next; } return true ; } bool listInsertHead (LinkList &list,E es[], int len) { for (int i = 0 ; i < len; ++i) { if (!insertNextNode (list, es[i])) return false ; } return true ; } void clear (LinkList &list) { LNode *p = list->next; list->next = nullptr ; while (p != nullptr ) { LNode *temp = p; p = p->next; free (temp); } }
表的总结 不难看出,两种表各有优劣,链表的增删速度快但查找的速度慢,而顺序表的增删速度慢,查找速度快,需要根据具体场景进行选择。
栈(Stack) 栈的本质也是一种表,只不过是一种操作受限的表,它限制元素的插入和删除都只能从表的一端进行,由此带来了后进先出(LIFO)的特性,所以对于栈的实现可以使用链表或者顺序表。
栈的顺序表实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 #include <cstdlib> #define E int #define DEFAULT_CAPACITY 10 typedef struct stack { E *data; int capacity; int top; } SqStack; bool initStack (SqStack &stack, int initCapacity) { if (initCapacity <= 0 ) return false ; stack.top = -1 ; stack.capacity = initCapacity; E *p = (E *) malloc (initCapacity * sizeof (E)); if (p == nullptr ) return false ; stack.data = p; return true ; } bool initStack (SqStack &stack) { return initStack (stack,DEFAULT_CAPACITY); } bool increaseStack (SqStack &stack, int newCapacity) { if (newCapacity < stack.capacity) return false ; E *oldData = stack.data; E *p = (E *) malloc (newCapacity * sizeof (E)); if (p == nullptr ) return false ; stack.data = p; for (int i = 0 ; i <= stack.top; ++i) { stack.data[i] = oldData[i]; } stack.capacity = newCapacity; free (oldData); return true ; } bool empty (SqStack &stack) { return stack.top == -1 ; } bool full (SqStack &stack) { return stack.top == stack.capacity - 1 ; } bool push (SqStack &stack,E e) { if (full (stack)) return false ; stack.data[++stack.top] = e; return true ; } bool pop (SqStack &stack, E &e) { if (empty (stack)) return false ; e = stack.data[stack.top--]; return true ; } bool top (SqStack &stack, E &e) { if (empty (stack))return false ; e = stack.data[stack.top]; return true ; } void clear (SqStack &stack) { stack.top = -1 ; }
栈的链表实现 对于栈的链表实现使用的是不带头节点的方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <cstdlib> #define E int typedef struct node { E data; node *next; } LNode, *linkStack; bool initStack (linkStack &stack) { stack = nullptr ; return true ; } bool empty (linkStack &stack) { return stack == nullptr ; } bool push (linkStack &stack,E e) { LNode *p = (LNode *) malloc (sizeof (LNode)); if (p == nullptr ) return false ; p->data = e; p->next = stack; stack = p; return true ; } bool pop (linkStack &stack,E &e) { if (empty (stack)) return false ; LNode *p = stack; stack = p->next; e = p->data; free (p); return true ; } bool top (linkStack &stack,E &e) { if (empty (stack)) return false ; e = stack->data; return true ; } void clear (linkStack &stack) { while (!empty (stack)) { E temp; pop (stack,temp); } }
栈的总结 栈是一种十分重要的数据结构,它在计算机系统中的使用随处可见。 在使用栈时要根据具体场景选择具体的实现方式。
队列(Queue) 队列的本质也是一种表,只不过是一种操作受限的表,它限制元素的插入只能从表的一端进行而删除要从另一端进行,由此带来了先进先出(FIFO)的特性,所以对于队列的实现可以使用链表或者顺序表。
队列的顺序表实现 以下是循环队列的实现,为了区分队头和队尾需要在队尾处空出一个储存空间,若想节省这一处空间,可以使用 size 记录大小或者操作标记来区区分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #include <cstdlib> #define E int #define DEFAULT_CAPACITY 10 typedef struct SqQueue { E *data; int front, rear; int capacity; } SqQueue; bool initQueue (SqQueue &queue, int initCapacity) { if (initCapacity <= 0 ) return false ; queue.front = 0 ; queue.rear = 0 ; E *p = (E *) malloc (initCapacity * sizeof (E)); if (p == nullptr ) return false ; queue.data = p; queue.capacity = initCapacity; return true ; } bool initQueue (SqQueue &queue) { return initQueue (queue,DEFAULT_CAPACITY); } bool full (SqQueue &queue) { return (queue.rear + 1 ) % queue.capacity == queue.front; } bool empty (SqQueue &queue) { return queue.front == queue.rear; } bool enQueue (SqQueue &queue,E e) { if (full (queue)) return false ; queue.data[queue.rear] = e; queue.rear = (queue.rear + 1 ) % queue.capacity; return true ; } bool deQueue (SqQueue &queue,E &e) { if (empty (queue)) return false ; e = queue.data[queue.front]; queue.front = (queue.front + 1 ) % queue.capacity; return true ; } bool getHead (SqQueue &queue,E &e) { if (empty (queue)) return false ; e = queue.data[queue.front]; return true ; } int length (SqQueue &queue) { return (queue.rear + queue.capacity - queue.front) % queue.capacity; } void clear (SqQueue &queue) { queue.front = 0 ; queue.rear = 0 ; }
使用操作标记的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <cstdlib> #define E int #define DEFAULT_CAPACITY 10 typedef struct SqQueue { E *data; int front, rear; int capacity; bool tag; } SqQueue; bool initQueue (SqQueue &queue, int initCapacity) { if (initCapacity <= 0 ) return false ; queue.front = 0 ; queue.rear = 0 ; E *p = (E *) malloc (initCapacity * sizeof (E)); if (p == nullptr ) return false ; queue.data = p; queue.capacity = initCapacity; queue.tag = false ; return true ; } bool initQueue (SqQueue &queue) { return initQueue (queue,DEFAULT_CAPACITY); } bool full (SqQueue &queue) { return queue.front == queue.rear && queue.tag == true ; } bool empty (SqQueue &queue) { return queue.front == queue.rear && queue.tag == false ; } bool enQueue (SqQueue &queue,E e) { if (full (queue)) return false ; queue.data[queue.rear] = e; queue.rear = (queue.rear + 1 ) % queue.capacity; queue.tag = true ; return true ; } bool deQueue (SqQueue &queue,E &e) { if (empty (queue)) return false ; e = queue.data[queue.front]; queue.front = (queue.front + 1 ) % queue.capacity; queue.tag = false ; return true ; }
队列的链表实现 对于队列的链表实现使用的是带头节点的方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 typedef struct Node { E data; Node *next; } LNode; typedef struct LinkQueue { LNode *front; LNode *rear; } LinkQueue; bool initQueue (LinkQueue &queue) { LNode *p = (LNode *) malloc (sizeof (LNode)); if (p == nullptr ) return false ; queue.front = p; queue.rear = p; return true ; } bool empty (LinkQueue &queue) { return queue.front == queue.rear; } bool enQueue (LinkQueue &queue,E e) { LNode *p = (LNode *) malloc (sizeof (LNode)); if (p == nullptr ) return false ; p->next = nullptr ; p->data = e; queue.rear->next = p; queue.rear = p; return true ; } bool deQueue (LinkQueue &queue,E &e) { if (empty (queue)) return false ; LNode *p = queue.front->next; e = p->data; queue.front = p->next; free (p); return true ; } bool getHead (LinkQueue &queue,E &e) { if (empty (queue)) return false ; e = queue.front->next->data; return true ; } void clear (LinkQueue &queue) { LNode *p = queue.front->next; while (p != nullptr ) { LNode *temp = p; p = p->next; free (temp); } queue.front->next = nullptr ; queue.rear = queue.front; }
双端队列 双端队列的入队和出队操作可以从两端进行,所以说栈和队列能实现的功能双端队列也能实现。