首页 > 代码库 > 第十章 基本数据结构

第十章 基本数据结构

摘要

  本章介绍了几种基本的数据结构,包括栈、队列、链表以及有根树,讨论了使用指针的简单数据结构来表示动态集合。本章的内容对于学过数据结构的人来说,没有什么难处,简单的总结一下。

1、栈和队列

  栈和队列都是动态集合,元素的出入是规定好的。栈规定元素是先进后出(FILO),队列规定元素是先进先出(FIFO)。栈和队列的实现可以采用数组和链表进行实现。在标准模块库STL中有具体的应用,可以参考http://www.cplusplus.com/reference/。

  栈的基本操作包括入栈push和出栈pop,栈有一个栈顶指针top,指向最新如栈的元素,入栈和出栈操作操作都是从栈顶端进行的。

  队列的基本操作包括入队enqueue和出队dequeue,队列有队头head和队尾tail指针。元素总是从队头出,从队尾入。采用数组实现队列时候,为了合理利用空间,可以采用循环实现队列空间的有效利用。

  关于栈和队列的基本操作如下图所示:

采用数组简单实现一下栈和队列,实现队列时候,长度为n的数组最多可以含有n-1个元素,循环利用,这样方便判断队列是空还是满。

栈的C++程序如下所示:

#include<iostream>#include<cstdlib>using namespace std;typedef struct stack{    int *s;    int stacksize;    int top;}stack;void init_stack(stack *s,int n){    s->stacksize=n;    s->s=(int*)malloc(sizeof(int)*s->stacksize);    s->top=0;}bool stack_empty(stack *s){    if(s->top==0)        return true;    else        return false;}bool stack_full(stack *s){    if(s->top==s->stacksize)        return true;    else        return false;}void push(stack *s,int x){    if(stack_full(s))    {        cout<<"overflow"<<endl;        exit(1);    }    s->top=s->top+1;    s->s[s->top]=x;}int pop(stack *s){    int x;    if(stack_empty(s))    {        cout<<"underflow"<<endl;        exit(1);    }    x=s->s[s->top];    s->top--;    return x;}int top(stack *s){    return s->s[s->top];}int main(){    stack s;    init_stack(&s,100);    push(&s,19);    push(&s,23);    push(&s,34);    push(&s,76);    push(&s,65);    cout<<"top is "<<top(&s)<<endl;    pop(&s);    cout<<"top is "<<top(&s)<<endl;}

队列的C++代码实现:

#include<iostream>#include<cstdlib>using namespace std;typedef struct queue{    int *q;    int head,tail;    int queuesize;}queue;void init_queue(queue *q,int n){    q->queuesize=n;    q->q=(int*)malloc(sizeof(int)*q->queuesize);    q->tail=q->head=0;}bool queue_empty(queue *q){    if(q->head==q->tail)        return true;    else        return false;}bool queue_full(queue *q){    if(q->tail+1==q->head)        return true;    else        return false;}int queue_length(queue *q){    return (q->tail-q->head+q->queuesize)%q->queuesize;}void enqueue(queue *q,int x){    if(queue_full(q))    {        cout<<"queue overflow"<<endl;        exit(1);    }    q->q[q->tail]=x;    q->tail=(q->tail+1)%q->queuesize;}int dequeue(queue *q){    int x;    if(queue_empty(q))    {        cout<<"queue underflow"<<endl;        exit(1);    }    x=q->q[q->head];    q->head=(q->head+1)%q->queuesize;    return x;}int main(){    queue q;    init_queue(&q,100);    enqueue(&q,10);    enqueue(&q,30);    cout<<"head: "<<q.head<<" tail: "<<q.tail<<endl;    cout<<"value=http://www.mamicode.com/"<<dequeue(&q)<<endl;    cout<<"value=http://www.mamicode.com/"<<dequeue(&q)<<endl;    cout<<"head: "<<q.head<<" tail: "<<q.tail<<endl;    enqueue(&q,10);    exit(0);}

问题:

(1)说明如何用两个栈实现一个队列,并分析有关队列操作的运行时间。

解答:栈中的元素是先进后出,而队列中的元素是先进先出。现有栈s1和s2,s1中存放队列中的结果,s2辅助转换s1为队列。

入队时,将元素压入s1。

出队时,判断s2是否为空,如不为空,则直接弹出顶元素;如为空,则将s1的元素逐个“倒入”s2,把最后一个元素弹出并出队。

(如果s1满了,s2既没有装满也不是非空,此时就不能继续入队了;如果s1满了,但s2是空的,则可以先将s1中的元素压人s2中,然后再进队。)

#include<iostream>#include<stack>#include<cstdlib>using namespace std;template <typename T>class StackToQueue{public:    T dequeue();    void enqueue(const T &node);    StackToQueue(){}    ~StackToQueue(){}private:    stack<T> stack1;    stack<T> stack2;};template <typename T>void StackToQueue<T>::enqueue(const T &node){    stack1.push(node);}template <typename T>T StackToQueue<T>::dequeue(){    if(stack2.empty()&&stack1.empty())    {        cout<<"underflow"<<endl;        exit(1);    }    if(stack2.empty()&&!stack1.empty())    {        while(!stack1.empty())        {            T temp=stack1.top();            stack1.pop();            stack2.push(temp);        }    }    T data=stack2.top();    stack2.pop();    return data;}int main(){    StackToQueue<int> sq;    sq.enqueue(1);    sq.enqueue(2);    sq.enqueue(3);    cout<<sq.dequeue()<<endl;    cout<<sq.dequeue()<<endl;    cout<<sq.dequeue()<<endl;    cout<<sq.dequeue()<<endl;}

(2)说明如何用两个队列实现一个栈,并分析有关栈操作的运行时间。

需要注意的一点是,如果每次入栈都选则将元素插入到第一个队列中,出队时先将前n-1个元素移交到第二个队列中,然后返回队列一中剩余的唯一一个元素,再将队列二中的元素又依次移交回队列一中,这样做未免效率过于低下。

事实上这样的思路我们可以看作是一直选用队列一作为栈元素的存储容器,而使用队列二作为一个出队时的辅助工具,这样每次出栈时来回复制元素容器元素的操作,效率确实低下。因为两个队列完全一样的,所以我们完全有理由让两个队列轮流作为栈元素的存储容器,这样每次出栈时,只需将所有前n-1个元素从一个队列移交到另一个队列中,然后返回最后一个元素作为出栈结果,这样始终至少有一个队列是空的,而每次进栈时,只需将进占元素放在那个非空队列的末尾(如果两个队列均为空,则随便放在那个里面都行)。这样减少了一半的复制操作。

#include<iostream>#include<queue>#include<cstdlib>using namespace std;template <typename T>class QueueToStack{public:    QueueToStack(){}    ~QueueToStack(){}    T pop();    void push(const T &node);private:    queue<T> queue1;    queue<T> queue2;};template <typename T>T QueueToStack<T>::pop(){    T data;    if(queue1.empty()&&queue2.empty())    {        cout<<"underflow"<<endl;        exit(1);    }    if(!queue1.empty())//对工作的队列进行操作    {
     //出队到队列中只剩下一个元素,就可以出栈了
while(queue1.size()>1) { T temp=queue1.front(); queue1.pop(); queue2.push(temp); } data=queue1.front(); queue1.pop(); } else if(!queue2.empty()) { while(queue2.size()>1) { T temp=queue2.front(); queue2.pop(); queue1.push(temp); } data=queue2.front(); queue2.pop(); } return data;}template <typename T>void QueueToStack<T>::push(const T &node){ if(!queue1.empty())//每次都压入到工作的队列中 queue1.push(node); else queue2.push(node);}int main(){ QueueToStack<int> queue; queue.push(1); queue.push(2); queue.push(3); cout<<queue.pop()<<endl; cout<<queue.pop()<<endl; cout<<queue.pop()<<endl;}

 

第十章 基本数据结构