最基本的数据结构:栈、队列、链表、二叉树。这一篇主要实现前三种数据结构,记录一部分习题的思路。这一篇比较简单,但算是学习更高级数据结构的良好开端吧。
栈
先进后出。最主要的方法是Push(element)个Pop(),前者压入一个元素,后者弹出一个元素。用数组实现一个简单的栈。
/*Stack*/ template <typename T> class xStack{ public: xStack(int size); bool push(T input); T pop(); private: T* arrayData; int top; int size; }; template <typename T> xStack<T>::xStack(int size){ arrayData = new T[size]; top = -1; this->size = size; } template <typename T> bool xStack<T>::push(T input){ if(top >= size-1){ return false; } else{ top++; arrayData[top] = input; } return true; } template <typename T> T xStack<T>::pop(){ if(top <= -1){ return NULL; } else{ int tmp = top; top--; return arrayData[tmp]; } }
队列
先进先出。最主要的方法是Enqueue(element)和Dequeue(),前者在队列尾部压入一个元素,后者从队列头部弹出一个元素。用数组实现一个简单的队列。
/*Queue*/ template <typename T> class xQueue{ public: xQueue(int size); bool enQueue(T input); T deQueue(); private: T* arrayData; int size; int head; int tail; int step(int pos); }; template <typename T> xQueue<T>::xQueue(int size){ arrayData = new T[size+1]; this->size = size; head = 0; tail = 0; } template <typename T> bool xQueue<T>::enQueue(T input){ if ((head == tail+1) || (head == 0 && tail == size)){ return false; } else{ arrayData[tail] = input; tail = step(tail); return true; } } template <typename T> T xQueue<T>::deQueue() { if (head == tail){ return NULL; } else{ int tmp = head; head = step(head); return arrayData[tmp]; } } template <typename T> int xQueue<T>::step(int pos) { if (pos < 0 || pos > size) { return -1; } else{ if (pos == size){ return 0; } else{ return pos+1; } } }
链表
链表(与其说是一种数据结构)不如说是一种组织数据的思想,即将指针作为结构体的一个属性以指向与结构体本身关联的其他同类型结构体。很多其他高级的数据结构实际上都是链表。这里仅仅简单实现一个单向链表。
/*LinkList*/ template <typename T> class xNode{ public: xNode(); xNode(T data); T data; xNode<T>* next; }; template <typename T> xNode<T>::xNode(){ this->next = NULL; } template <typename T> xNode<T>::xNode(T data){ this->data = data; this->next = NULL; } template <typename T> class xLinkList{ public: xLinkList(); xNode<T>* search(T value); void insert(T input); bool deleteNode(xNode<T>* node); xNode<T>* head; }; template <typename T> xLinkList<T>::xLinkList(){ head = new xNode<T>(); } template <typename T> xNode<T>* xLinkList<T>::search(T value){ xNode<T>* tmp = head->next; while (tmp != NULL) { if (tmp->data == value) { return tmp; } tmp = tmp->next; } return NULL; } template <typename T> void xLinkList<T>::insert(T input){ xNode<T>* tmp = new xNode<T>(input); tmp->next = head->next; head->next = tmp; } template <typename T> bool xLinkList<T>::deleteNode(xNode<T>* node){ xNode<T>* prevNode = NULL; xNode<T>* tmp = head; while(tmp != NULL) { if (tmp->next == node) { prevNode = tmp; break; } tmp = tmp->next; } if (prevNode == NULL) { return false; } prevNode->next = node->next; return true; }
练习10.1-2 使用一个数组实现两个栈,使得Push(element)和Pop()的代价都为 $O(1)$ 。思路:数组两端作为栈低,栈顶相向。
练习10.1-6 使用两个栈实现一个队列,分析操作代价。思路:一个空栈一个满栈,Enqueue(element)操作正常向满栈Push(element),代价为 $O(1)$;Dequeue()操作先将满栈全部Pop()出来,出来一个就压到空栈里面,结束后对空栈(此时非空了)Pop(),结果作为Dequeue的结果返回,再将空栈(此时非空)元素全部Pop()回满栈(此时已空),代价为 $O(n)$ 。
练习10.1-7 使用两个队列实现一个栈,分析操作代价。思路,一个空队列一个满队列,Push(element)操作正常向满队列Enqueue(element),代价为 $O(1)$ ;Pop()操作先将满队列元素一个一个Dequeue()出来并Enqueue(element)到另一个空队列里面,直到最后一个不执行Enqueue(element) ,却返回作为Pop()的返回值,代价为 $O(n)$ 。
练习10.2-8 对链表中的每个元素仅使用一个指针 np 而不是 next 和 pre ,实现双向列表。假设指针值为 $k$ 位整形数。提示使用异或的特性。
思路:对于二进制的 $k$ 位整数, 如果 A xor B = C 则 A xor C=B and B xor C=A 。所以设定 np 为该元素前一个元素地址和后一个元素地址的异或值,这样每知道一个节点的后一个节点地址,就可以知道前一个节点地址,反之亦可。第一个节点的地址是已知的(这样才能访问到该节点的 np 指针),而且第一个节点的前一个节点不存在,地址为 $0$ ,所以可以通过 0 xor np[1] 获得第二个节点的地址。
练习10.4-3 写出一个 $O(n)$ 时间的非递归过程,将给定的 $n$ 各节点的二叉树中的每个节点的 $key$ 值输出。利用栈作为辅助数据结构。
思路:从根节点开始迭代。首先,输出关键字。然后判断当前节点是否是叶子节点:如果不是,判断是否有右节点,若有则将右节点压入栈;不管是否有右节点,当前节点进入左节点继续迭代;如果是叶子节点,从栈中弹出一个节点作为当前节点。如果栈空了,则结束。
练习10.4-5 写一个 $O(n)$ 的非递归过程,将给定的 $n$ 各节点的二叉树中的每个节点的 $key$ 值输出。只能利用固定量的额外存储空间,也不能修改树(即使是暂时的)。
思路:抵达当前节点,先判断从何处抵达该节点:如果是从父节点到达当前节点的,先输出值,再判断是否是叶子节点,如果是,直接返回父节点,如果不是则进入左节点;如果是从左节点到达当前节点的,判断是否有右节点,如果有则进入右节点,如果没有则进入父节点;如果是从右节点进入该节点的,直接进入父节点。(这是前序的版本,也有中序和后序,差异就是在何处输出值。)该方法需要二叉树是双向链表,即左节点和右节点能够访问到父节点,否则还是需要用栈或者递归。