数据结构与算法分析-C++描述 第3章 表、栈和队列

1.表

下标从0开始,到n-1结束的序列,叫做大小为n的表。
STL中的表:
vector-索引快(),在非末尾的地方插入和删除慢。
list-双向链表,插入和删除快,索引慢。
这两个查找都慢。

方法:
对所有容器都适用的方法:
size() 返回元素个数
clear() 删除所有元素
empty() 判断是否为空

适用于vector和list的方法:
push_back(x) 在表末插入元素x
pop_back() 删除表末对象
back() 返回表末对象
front() 返回表头对象

只适用于list,不适用于vector的方法:
push_front(x) 在表头插入元素x
pop_front() 删除表头对象

只适用于vector,不适用于list的方法:
vector重载了[ ]运算符但list没有
capacity() 返回vector最多能装多少元素
reserve(x) 设置vector最多能装x个元素,但这些元素还未初始化
resize(x) 设置vector最多能装x个元素,且这些元素已经被默认构造函数初始化了

两者都适用,但需要使用迭代器的方法:
insert(iterator,x) 在iterator的 前面 插入x,返回插入的地方,list快于vector
erase(iterator) 删掉iterator指向的对象 ,返回删掉的后面,list快于vector,删完了iterator就不存在了
erase(iterator1,iterator2) 删掉[iterator1,iterator2)之间的对象

2.vector的实现

#pragma once
template
class Vector {
private:
    int thesize;   //容量
    int thecapacity;  //最大容量
    Object *objects;  //指针
public:
    enum { SPARE_CAPACITY = 16 }; //容器的最大容量
    explicit Vector(int initsize = 0) //构造函数
        : thesize(initsize), thecapacity(initsize + SPARE_CAPACITY)
    {
        objects = new Object[thecapacity];
    }
    Vector(const Vector & rhs) : objects(NULL)//拷贝构造函数
    {
        operator = (rhs); //*this.operator = (rhs) 相当于*this = rhs
    }
    ~Vector() {
        delete[] objects;
    }//析构函数,要delete掉new出来的objects
    const Vector & operator = (const Vector & rhs) { //重载的赋值运算符
        if (this != &rhs) { //自赋值检测
            delete[] objects;
            thesize = rhs.size();
            thecapacity = rhs.thecapacity();
            objects = new Object[capacity()];
            for (int k = 0; k < size(); k++)
                objects[k] = rhs.objects[k]; //在赋值之前要重新更改容量
        }
        return *this;
    }
    void resize(int newsize) { //重新设置容量
        if (newsize > thecapacity)
            reserve(newsize * 2 + 1);
        thesize = newsize;
    }
    void reserve(int newcapacity) { //重新设置最大容量
        if (newcapacity > thesize) return;
        Object *oldarray = objects;
        objects = new Object[newcapacity];
        for (int k = 0; k < thesize; k++) {
            objects[k] = oldarray[k];
        }
        thecapacity = newcapacity;
        delete[] oldarray;
    }
    Object & operator[](int index) {//重载的[]运算符
        return objects[index];
    }
    bool empty() const {//判断Vector是否为空
        return size() == 0;
    }
    int size() const {//返回容量
        return thesize;
    }
    int capacity() const {//返回最大容量
        return thecapacity;
    }
    void push_back(const Object & x) {//添加末尾元素
        if (thesize == thecapacity) reserve(2 * thecapacity + 1);
        objects[thesize++] = x;
    }
    void pop_back() { //删除末尾元素
        thesize--;
    }
    const Object & back() const { //返回末尾元素
        return objects[thesize - 1];
    }
    //把指针命名成迭代器
    typedef Object* iterator;
    typedef const Object* const_iterator;

    iterator begin() {//begin()迭代器
        return &objects[0];
    }

    const_iterator begin() const {
        return &objects[0];
    }

    iterator end() {//end()迭代器
        return &objects[size()];
    }

    const_iterator end() const {
        return &objects[size()];
    }
};

3.链表的实现

这本书上的实现太复杂了,先用之前课件上的,以后有时间再补。
单向链表

#include
using namespace std;
struct Node {
    int data;
    Node *next;
};
Node* creat() {//创建链表并返回头节点
    Node *head, *tail, *pnew;
    //*tail:尾结点,tail->next:尾指针
    //头节点是第0个结点
    head = new Node;
    head->next = NULL;  //堵住最后
    tail = head;  //尾结点和头结点重合
    while (1) {
        pnew = new Node;
        cin >> pnew->data;
        if (pnew->data == -1) {
            delete pnew;
            break;
        }
        tail->next = pnew;  //让尾指针指向新节点
        tail = pnew;  //让新节点变成尾结点
        pnew->next = NULL;  //堵住最后
        //上述三行加起来实现了把一个新节点挂在原来尾结点的后面
    }
    return head;
}
void insert(Node *head, Node *pnew, int ith) {//把某个节点插入到第ith个结点的后面
    Node *p = head; 
    for (int i = 0; i < ith && p!=NULL ; i++) p = p->next;   //跑完以后p就到了第ith个结点处了
    if (p == NULL)
    {
        cout << "Error!" << endl;
        return;
    }
    pnew->next = p->next;   //将p的后继节点(第ith+1个结点)设为pnew的后继节点
    p->next = pnew;
    //将pnew设为p的后继节点
    //注意此处的顺序不能颠倒,否则后面的链表就都丢了
}

void del(Node *head, int ith) {  //删除第ith个结点
    if (ith == 0) { //不能删头节点
        cout << "Error!" << endl;
        return;
    }
    Node *p = head, *q;
    for (int i = 1; i < ith && p->next != NULL; i++) p = p->next;
    //注意此处是要让i=ith-1,来删掉p后面的那个结点,故判断应为p->next!=NULL
    if (p->next == NULL) {
        cout << "Error!" << endl;
        return;
    }
    q = p->next; //让q指向要删的结点
    p->next = q->next;  //将要删的结点的后置结点设为p的后置结点
    delete q;
}

void show(Node *head) { //输出整个链表
    Node *p;
    for (p = head->next; p != NULL; p = p->next) {  //遍历链表,注意不要从头节点开始
        cout << p->data << ' ';
    }
    cout << endl;
}

void clear(Node* head) {  //销毁整个链表
    Node *p = new Node;
    while (head->next != NULL) {
        p = head;
        head = p->next;
        delete p;
    }
}


int main() {
    Node *head = creat();
    show(head);
    //这里有个坑,如果要添加新结点的话必须用下面这种new的方法,从堆里分配*p的空间
    //不能把一个Node结构的地址传给insert函数,否则del的时候会delete一个栈空间,从而报错
    Node *p = new Node;
    p->data = 1;
    p->next = NULL;
    insert(head, p, 2);
    show(head);
    del(head, 3);
    show(head);
    clear(head);
    return 0;
}


在单向链表上稍作修改,又添加了逆序输出的函数。

#include
using namespace std;
struct Node {
    int data;
    Node *next;
    Node *previous;
};
Node* creat() {//创建链表并返回头节点
    Node *head, *tail, *pnew;
    //*tail:尾结点,tail->next:尾指针
    //头节点是第0个结点
    head = new Node;
    head->next = NULL;  //堵住最后
    head->previous = NULL;
    tail = head;  //尾结点和头结点重合
    while (1) {
        pnew = new Node;
        cin >> pnew->data;
        if (pnew->data == -1) {
            delete pnew;
            break;
        }
        pnew->previous = tail;
        tail->next = pnew;  //让尾指针指向新节点
        tail = pnew;  //让新节点变成尾结点
        pnew->next = NULL;  //堵住最后
        //上述三行加起来实现了把一个新节点挂在原来尾结点的后面
    }
    return head;
}
void insert(Node *head, Node *pnew, int ith) {//把某个节点插入到第ith个结点的后面
    Node *p = head;
    for (int i = 0; i < ith && p != NULL; i++) p = p->next;   //跑完以后p就到了第ith个结点处了
    if (p == NULL)
    {
        cout << "Error!" << endl;
        return;
    }
    pnew->previous = p;
    pnew->next = p->next;
    p->next->previous = pnew;
    p->next = pnew;
    //注意此处的顺序不能颠倒,否则后面的链表就都丢了
}

void del(Node *head, int ith) {  //删除第ith个结点
    if (ith == 0) { //不能删头节点
        cout << "Error!" << endl;
        return;
    }
    Node *p = head, *q;
    for (int i = 1; i < ith && p->next != NULL; i++) p = p->next;
    //注意此处是要让i=ith-1,来删掉p后面的那个结点,故判断应为p->next!=NULL
    if (p->next == NULL) {
        cout << "Error!" << endl;
        return;
    }
    q = p->next; //让q指向要删的结点
    p->next = q->next;  //将要删的结点的后置结点设为p的后置结点
    q->next->previous = p;
    delete q;
}

void show(Node *head) { //输出整个链表
    Node *p;
    for (p = head->next; p != NULL; p = p->next) {  //遍历链表,注意不要从头节点开始
        cout << p->data << ' ';
    }
    cout << endl;
}

void revshow(Node *tail) {
    Node *p;
    for (p = tail; p->previous != NULL; p = p->previous) {
        cout << p->data << ' ';
    }
    cout << endl;
}


void clear(Node* head) {  //销毁整个链表
    Node *p = new Node;
    while (head->next != NULL) {
        p = head;
        head = p->next;
        delete p;
    }
}


int main() {
    Node *head = creat();
    show(head);
    //这里有个坑,如果要添加新结点的话必须用下面这种new的方法,从堆里分配*p的空间
    //不能把一个Node结构的地址传给insert函数,否则del的时候会delete一个栈空间,从而报错
    Node *tail;
    for (tail = head; tail->next != NULL; tail = tail->next);
    revshow(tail);
    Node *p = new Node;
    p->data = 1;
    p->next = NULL;
    insert(head, p, 2);
    revshow(tail);
    del(head, 3);
    revshow(tail);
    clear(head);
    return 0;
}

循环链表
就是让链表的尾指针指向头节点。

4.栈

插入和删除操作只能在表末进行的表,这里把表末叫做栈顶。
push(进栈),pop(出栈),后进先出。
栈可以由stack,vector或list实现。
应用:
判断括号有没有匹配:
‘(’后面一定紧跟着‘)’,[],{}也同理
栈可以用来保存后面的状态。

#include
#include
#include
#include
using namespace std;
map judge;
stack test;
int main() {
    judge[')'] = '(';
    judge[']'] = '[';
    judge['}'] = '{';
    string input;
    cin >> input;
    int len = input.size();
    for (int i = 0; i < len; i++) {
        if (judge.find(input[i]) == judge.end())
            test.push(input[i]);
        else if (test.empty()) {
            cout << "No";
            return 0;
        }
        else if (test.top() != judge[input[i]]){
            cout << "No";
            return 0;
        }
        else test.pop();
    }
    if (test.empty()) cout << "Yes";
    else cout << "No";
    return 0;
}

后缀表达式求值(整数)
碰到运算符就让栈顶的两个元素出栈做运算。

#include
#include
#include
#include
using namespace std;
string input;
stack cal;
int rnum;
int lnum;
int main() {
    getline(cin, input);
    int len = input.size();
    for (int i = 0; i < len; i += 2) {
        switch (input[i]) {
        case '+':
            rnum = cal.top();
            cal.pop();
            lnum = cal.top();
            cal.pop();
            cal.push(lnum + rnum);
            break;
        case'-':
            rnum = cal.top();
            cal.pop();
            lnum = cal.top();
            cal.pop();
            cal.push(lnum - rnum);
            break;
        case'*':
            rnum = cal.top();
            cal.pop();
            lnum = cal.top();
            cal.pop();
            cal.push(lnum * rnum);
            break;
        case'/':
            rnum = cal.top();
            cal.pop();
            lnum = cal.top();
            cal.pop();
            cal.push(lnum / rnum);
            break;
        default:
            cal.push(input[i] - '0');
        }
    }
    cout << cal.top();
    return 0;
}

前缀表达式求值(浮点数)
http://bailian.openjudge.cn/practice/2694/
先翻转成后缀表达式,注意减和除的顺序。

#include
#include
#include
#include
#include
#include
using namespace std;
stack cal;
string in;
string input[100000];
double rnum;
double lnum;
double temp;
int main() {
    getline(cin, in);
    stringstream ss;
    ss << in;
    int num = 0;
    while (ss >> input[num++]);
    reverse(input, input + num);
    for (int i = 1; i < num; i++) {
        switch (input[i][0]) {
        case '+':
            rnum = cal.top();
            cal.pop();
            lnum = cal.top();
            cal.pop();
            cal.push(rnum + lnum);
            break;
        case'-':
            rnum = cal.top();
            cal.pop();
            lnum = cal.top();
            cal.pop();
            cal.push(rnum - lnum);
            break;
        case'*':
            rnum = cal.top();
            cal.pop();
            lnum = cal.top();
            cal.pop();
            cal.push(rnum * lnum);
            break;
        case'/':
            rnum = cal.top();
            cal.pop();
            lnum = cal.top();
            cal.pop();
            cal.push(rnum / lnum);
            break;
        default:
            stringstream stemp;
            stemp << input[i];
            stemp >> temp;
            cal.push(temp);
            break;
        }
    }
    cout << fixed << setprecision(6) << cal.top();
    return 0;
}

中缀表达式转后缀表达式
比较和,前者的后缀表达式为a b c * +,后者为a b + c *,显然输出字母的顺序跟运算的优先级有关, ,低级运算符碰上高级运算符,不能马上输出;反之可以,因为高级肯定算完了。把中缀表达式读入:

如果遇到数字,立即输出;如果遇到运算符和左括号,从栈中输出并弹出元素直到栈顶元素的优先级比遇到的运算符低,把遇到的运算符压入栈中;如果遇到右括号,则将栈内元素输出并弹出,直到栈顶为左括号,把左括号弹出但不输出。最后,将栈里的所有元素全部弹出。

#include
#include
#include
#include
#include
using namespace std;
string input;
map prior;
stack cal;
int main() {
    prior['('] = 3;
    prior['*'] = prior['/'] = 2;
    prior['+'] = prior['-'] = 1;
    getline(cin, input);
    int len = input.size();
    for (int i = 0; i < len; i++) {
        if (isspace(input[i])) continue;
        if (isalnum(input[i])) cout << input[i] << ' ';
        else{
            if (input[i] == ')') {
                while (cal.top() != '(') {
                    cout << char(cal.top()) << ' ';
                    cal.pop();
                }
                cal.pop();
            }
            else {
                while (!cal.empty() && prior[input[i]] <= prior[cal.top()]) {
                    if (cal.top() == '(') break;//左括号里面的运算符输出到左括号为止
                    cout << char(cal.top()) << ' ';
                    cal.pop();
                }
                cal.push(input[i]);
            }
        }
    }
    while (!cal.empty()) {
        cout << char(cal.top()) << ' ';
        cal.pop();
    }
    return 0;
}

中缀表达式求值:结合和即可

5.队列

先进先出的容器。
入队:在队尾插入一个元素,出队:在队头删除一个元素。
双端队列:队头和队尾都可以修改元素。
STL deque的方法:
比vector多了front(),pop_front(),push_front(x)。
重载了[]运算符。
应用:广度优先搜索
框架:

BFS()
{
    初始化队列
    while(队列不为空)
    {
        取队首结点扩展,并将扩展出的结点放入队尾
        队首结点出队
                必要时要记住每个结点的父结点(搞个结构体当队列的)
    }
}

例题:洛谷P1443

你可能感兴趣的:(数据结构与算法分析-C++描述 第3章 表、栈和队列)