开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 29 天,点击查看活动详情
什么是数据结构
数据结构是一种在程序中系统化管理数据集合的形式。
数据结构由三部分组成:
- 数据集合:就是用于存放一个个数据对象的集合(比如数组、结构体等)
- 规则:保证数据集合按照一定规矩进行正确操作 、 管理和保存的规则
- 操作:包含了对数据集合进行的操作,比如插入元素、取出元素、查询元素,检查元素等
下面记录两个基本的数据结构:栈 和 队列。
栈
栈(Stack)是一种常见的数据结构(能有效帮助我们临时保存数据)。
栈的常见操作有:
push(x)
:在栈的顶部添加元素 xpop()
:从栈的顶部取出元素isEmpty()
:检查栈是否为空isFull()
:检查栈是否已满
栈的其他操作有:"引用栈顶元素"、"检查栈中是否含有指定元素"
栈的规则:
- 在一个栈中,最后进入的元素一定是最先被取出的,即后入先出
- 按照最后进入栈的数据最后出栈的规则进行管理数据。
pop()
取出的元素是最后一次被push(x)
的元素
队列
队列(Queue)也是一种常见的数据结构(一个等待处理的行列)。
队列的常见操作有:
enqueue(x)
:从队列末尾取出元素dequeue(c)
:从队列开头取出元素isEmpty()
:检查队列是否为空isFull()
:检查队列是否已满
队列的其他操作有:"引用队头元素"、"检查队列中是否含有指定元素"
队列的规则:
- 在一个队列中,元素必须按照抵达的先后顺序被处理
- 最先放入的元素最先被取出,即先入先出
- 距上一次
enqueue
时间最久的元素最先被取出
标准库的数据结构
C++库以提供标准 “模板”为主,我们可以借助标准模板库(STL)提供的高效算法来管理数据。
为了应对多种需求,STL 为用户提供了多种名为容器(Container)的类,用于管理数据集合。在创建栈、队列、动态数组等数据结构时,我们只需要定义对应的容器,然后调用相应的成员函数或者算法即可。
下面记录四种常见的容器。
stack
我们可以借助 stack
来实现栈,用它管理数据。
在使用之前,我们需要将 STL 中的 stack
包含到程序中,即:
#include<stack>
好了,现在我们来声明一个栈 s
,用于管理 int
类型的元素。
stack<int> s;
如上所示,STL 提供的 stack
是一个模板,我们需要在 <>
中指定栈容器要管理的数据类型。
有了栈 s
后,我们可以调用其成员函数,如下所示:
push(x)
:向栈中添加元素x
pop()
:从栈中取出元素并删除元素size()
:返回栈的元素数top()
:返回栈顶的元素empty()
:判断栈是否为空
下面举例了栈的使用:
#include<iostream>
#include<stack>
using namespace std;
int main() {
// 声明一个栈 s
stack<int> s;
// 向栈中压入三个元素:5 9 2
cout << "向栈中分别压入元素:\n 5 9 2" << endl;
s.push(5);
s.push(9);
s.push(2);
// 查看栈的大小
cout << "当前栈的大小为:" << s.size() << endl;
// 查看栈顶元素
cout << "当前栈顶元素为:" << s.top() << endl;
// 从栈顶删除元素
cout << "删除栈顶元素..." << endl;
s.pop();
// 查看新的栈顶元素
cout << "当前新的栈顶元素为:" << s.top() << endl;
// 删除两个元素
cout << "删除两个元素" << endl;
s.pop();
s.pop();
// 查看当前栈是否为空
if(s.empty()) {
cout << "栈已为空" << endl;
} else {
cout << "栈不为空" << endl;
}
return 0;
}
运行结果:
向栈中分别压入元素:
5 9 2
当前栈的大小为:3
当前栈顶元素为:2
删除栈顶元素...
当前新的栈顶元素为:9
删除两个元素
栈已为空
queue
我们可以借助 queue
来实现队列,用它管理数据。
在使用之前,我们需要将 STL 中的 queue
包含到程序中,即:
#include<queue>
好了,现在我们来声明一个队列 q
,用于管理 string
类型的元素。
queue<string> q;
如上所示,STL 提供的 queue
是一个模板,我们需要在 <>
中指定栈容器要管理的数据类型。
有了队列 q
后,我们可以调用其成员函数,如下所示:
push(x)
:向队列中添加元素x
pop()
:从队列中取出元素并删除元素size()
:返回队列的元素数front()
:返回队头的元素empty()
:判断队列是否为空
下面举例了栈的使用:
#include<iostream>
#include<queue>
#include<string>
using namespace std;
int main() {
// 声明一个队列 q
queue<string> q;
// 向队列中添加三个元素:one two three
cout << "向栈中分别压入元素:\n one two three" << endl;
q.push("one");
q.push("two");
q.push("three");
// 查看队列的大小
cout << "当前队列的大小为:" << q.size() << endl;
// 查看队头元素
cout << "当前队头元素为:" << q.front() << endl;
// 从队头删除元素
cout << "删除队头元素..." << endl;
q.pop();
// 查看新的队头元素
cout << "当前新的队头元素为:" << q.front() << endl;
// 删除两个元素
cout << "删除两个元素" << endl;
q.pop();
q.pop();
// 查看当前队列是否为空
if(q.empty()) {
cout << "队列为空" << endl;
} else {
cout << "队列不为空" << endl;
}
return 0;
}
运行结果:
向栈中分别压入元素:
one two three
当前队列的大小为:3
当前队头元素为:one
删除队头元素...
当前新的队头元素为:two
删除两个元素
队列为空
vector
我们可以借助 vector
来实现一个动态数组,用它管理数据。
在之前,我们自己定义的数组都是定长数组,即定义完数组的大小之后,不能改变其大小。这种静态数组是只能容纳一定数量元素的数组。但是,借助 vector
,我们可以定义可变长数组,这种动态数组可以在添加元素时增加长度。
在使用动态数组之前,我们需要将 STL 中的 vector
包含到程序中,即:
#include<vector>
好了,现在我们来声明一个动态数组 v
,用于管理 double
类型的元素。
vector<double> v;
如上所示,STL 提供的 vector
是一个模板,我们需要在 <>
中指定栈容器要管理的数据类型。
在访问动态数组中的元素,无论是赋值还是写入,都与普通数组一样使用
[]
操作符。
有了动态数组 v
后,我们可以调用其成员函数,如下所示:
push_back(x)
:将元素x
添加到动态数组的末尾pop_back()
:删除动态数组的最后一个元素size()
:返回动态数组的元素数insert(p, x)
:在动态数组中位置为p
处插入元素x
erase(p)
:删除动态数组中位置为p
的元素clear()
:删除动态数组中的所有元素begin()
:返回指向动态数组开头的迭代器end()
:返回指向动态数组最后一个元素的后一个位置的迭代器
这里的迭代器可以看成一个指针。
下面举例了动态数组的使用:
#include<iostream>
#include<vector>
using namespace std;
void print(vector<double> V) {
cout << "\n此时动态数组为:[" ;
for(int i = 0; i<V.size(); i++) {
cout << V[i];
if(i != V.size()-1) cout << ", ";
}
cout << "]\n\n";
}
int main() {
// 声明一个动态数组
vector<double> v;
// 添加三个元素
cout << "往动态数组中添加三个元素:\n 0.12 0.34 0.56" << endl;
v.push_back(0.12);
v.push_back(0.34);
v.push_back(0.56);
print(v);
// 修改动态数组中的第三个元素
cout << "修改v[2]为0.78" << endl;
v[2] = 0.78;
print(v);
// 往数组中第三个位置处插入一个元素
cout << "在第三个位置处插入元素 0.99" << endl;
v.insert(v.begin()+2, 0.99);
print(v);
// 删除动态数组中的首元素
cout << "删除动态数组中的首元素" << endl;
v.erase(v.begin());
print(v);
return 0;
}
运行结果:
往动态数组中添加三个元素:
0.12 0.34 0.56
此时动态数组为:[0.12, 0.34, 0.56]
修改v[2]为0.78
此时动态数组为:[0.12, 0.34, 0.78]
在第三个位置处插入元素 0.99
此时动态数组为:[0.12, 0.34, 0.99, 0.78]
删除动态数组中的首元素
此时动态数组为:[0.34, 0.99, 0.78]
list
我们可以借助 list
来实现一个双向链表,用它管理数据。
往双向链表中添加元素时,只需更改几个指针的指向即可,效率较高,其算法复杂度为 ,但是搜索算法的复杂度为 ,这种特点和 vector
刚好相反。
在使用双向链表之前,我们需要将 STL 中的 list
包含到程序中,即:
#include<list>
好了,现在我们来声明一个双向链表 L
,用于管理 char
类型的元素。
list<char> L;
如上所示,STL 提供的 list
是一个模板,我们需要在 <>
中指定栈容器要管理的数据类型。
list 既可以像
vector
一样通过 “[ ]” 运算符直接访问特定元素,也可以用迭代器逐个进行访问。
有了双向链表 L
后,我们可以调用其成员函数,如下所示:
push_front(x)
:将元素x
添加到双向链表的开头push_back(x)
:将元素x
添加到双向链表的末尾pop_front()
:删除双向链表的开头元素pop_back()
:删除双向链表的末尾元素size()
:返回双向链表的元素数insert(p, x)
:在双向链表中位置为p
处插入元素x
erase(p)
:删除双向链表中位置为p
的元素clear()
:删除双向链表中的所有元素begin()
:返回指向双向链表开头的迭代器end()
:返回指向双向链表最后一个元素的后一个位置的迭代器
下面举例了双向链表的使用:
#include<iostream>
#include<list>
using namespace std;
void print(list<char> l) {
cout << "\n此时双向链表为:" ;
auto it = l.begin();
while(1) {
cout << "[" << *it << "]";
it++;
if(it != l.end()) cout << " <--> ";
else break;
}
cout << "\n\n";
}
int main() {
// 声明一个双向链表
list<char> L;
// 添加三个元素
cout << "往双向链表中添加三个元素:\n a b c" << endl;
L.push_back('b');
L.push_front('a');
L.push_back('c');
print(L);
// 删除双向链表的首元素
cout << "删除双向链表的首元素" << endl;
L.pop_front(); // 或者:L.erase(L.begin());
print(L);
// 往双向链表的末尾处添加一个元素 d
cout << "往双向链表的末尾处添加一个元素 d" << endl;
L.push_back('d'); // 或者:L.insert(L.end(), 'd');
print(L);
return 0;
}
运行结果:
往双向链表中添加三个元素:
a b c
此时双向链表为:[a] <--> [b] <--> [c]
删除双向链表的首元素
此时双向链表为:[b] <--> [c]
往双向链表的末尾处添加一个元素 d
此时双向链表为:[b] <--> [c] <--> [d]