在学习数据结构中,栈、队列、链表是几个比较重要的学习点。如何让数据有序的存储呢,但是在这之前那我们得先学习一些储备知识,那样才会让我们更好的往下学习。
程序内存分配(基础知识)
- 栈(stack): 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 其操作如往箱子里面放衣服一样,只有一个出口,后进先出。
- 堆(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。其操作方式如同有出口和入口的景区,先进先出。
- 自由存储区:自由存储是C++中通过new与delete动态分配和释放对象的抽象概念,而堆(heap)是C语言和操作系统的术语,是操作系统维护的一块动态分配内存。(这里有点难理解,很多人认为自由存储区和堆是等价的,其实不然)
- 全局存储区(静态存储区):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后有系统释放。
- 常量存储区:这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。
示例描述:
//test.cpp
int a = 0; //全局初始化区
int a = 0; //全局初始化区
char *p1; //全局未初始化区
main() {
int b; //栈
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "123456"; //123456\0在常量区,p3在栈上。
static int c = 0; //全局(静态)初始化区
int* nub = new int;
int* nub1 = new int(5);
//分配在自由存储区 new
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
//分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
free(p1); //释放内存
free(p2);
delete nub; //释放内存
delete[] nub1;
}
数据结构:栈
栈:是一种连续存储的数据结构,特点是存储的数据先进后出。我们把表尾称作栈顶(top),相应的,表头称作栈底(bottom),由栈的特性可知,栈是一种后进先出(LIFO)的线性表,只能在栈顶进行插入和删除。包括:栈、链栈。
使用STL容器基于数组的栈操作:
#include <stack>
#include <iostream>
using namespace std;
int main()
{
stack<int> Nstack;
int sum = 0;
for (int i = 0; i <= 10; i++){
Nstack.push(i);
}
cout << "size is " << Nstack.size() << endl;
while (!Nstack.empty()){
cout << "-" << Nstack.top();
Nstack.pop();
}
cout << endl;
return 0;
}
// Nstack.empty();//如果栈为空则返回true, 否则返回false;
// Nstack.size(); //返回栈中元素的个数
// Nstack.top(); //返回栈顶元素, 但不删除该元素
// Nstack.pop(); //弹出栈顶元素, 但不返回其值
// Nstack.push(); //将元素压入栈顶
更多操作后续补充
数据结构:队列
队列:队列是一种先进先出(FIFO)的线性表,它只允许在一边插入,而在另一边删除元素,我们把删除的一端称为队首(front),插入的一端称为队尾(rear)。包括单向队列、双向队列、循环队列。
基于STL容器数组队列示例(单向队列):
#include <queue>
#include <iostream>
#include <cstdlib>
using namespace std;
int main(){
queue<int> q;
for (int i = 0; i < 10; i++){
q.push(i);
}
if (!q.empty()){
cout << "队列q非空!" << endl;
cout << "q中有" << q.size() << "个元素" << endl;
}
cout << "队头元素为:" << q.front() << endl;
cout << "队尾元素为:" << q.back() << endl;
for (int j = 0; j < 10; j++){
int tmp = q.front();
cout << tmp << " ";
q.pop();
}
cout << endl;
if (!q.empty()){
cout << "队列非空!" << endl;
}
system("pause");
return 0;
}
// q.empty() 如果队列为空返回true,否则返回false
// q.size() 返回队列中元素的个数
// q.pop() 删除队列首元素但不返回其值
// q.front() 返回队首元素的值,但不删除该元素
// q.push() 在队尾压入新元素
// q.back() 返回队列尾元素的值,但不删除该元素
更多操作后续补充
数据结构:链表
链表:链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。链表很好的克服了数组的缺点,它在内存中不需要连续的内存,插入或者删除操作o(1)时间复杂度就能解决,长度也是动态增长。如果你的元素需要频繁的进行插入、删除操作,那么链表就是个很好的选择。包括单向链表、双向链表、单向循环链表、双向循环链表。
简单链表实现:
//单向链表
#include <iostream>
using namespace std;
class node
{
public:
int value;
node *next;
node()
{
value = 0;
next = NULL;
}
};
int main()
{
node *head,*curr;
head = new node();
head->next = NULL;
head->value = 15;
for (size_t i = 0; i < 10; i++)
{
curr = new node();
curr->value = i;
curr->next = head;
head = curr;
}
//遍历
for(int i = 0 ; i< 11; i++){
cout << head->value <<endl;
curr = head->next;
head = curr;
}
}
使用STL容器实现链表:
#include<iostream>
#include<list> //双向链表
#include<algorithm>
//#include<forward_list> //单向链表
using namespace std;
typedef struct Node
{
int a;
char c;
bool operator==(struct Node b) const
{
return (this->a == b.a)&&(this->c == b.c);
}
bool operator!=(struct Node b) const
{
return (this->a == b.a)&&(this->c == b.c);
}
bool operator>=(struct Node b) const
{
return (this->a == b.a)&&(this->c == b.c);
}
bool operator<=(struct Node b) const
{
return (this->a == b.a)&&(this->c == b.c);
}
bool operator>(struct Node b) const
{
return (this->a == b.a)&&(this->c == b.c);
}
bool operator<(struct Node b) const
{
return (this->a == b.a)&&(this->c == b.c);
}
}Nodes;
void fun(Nodes& d)
{
cout << d.a << "-" << d.c << "\n";
}
int main()
{
Nodes nu={12,'a'};
Nodes nu1 = {20,'c'};
list<Nodes> ls(6,nu);
cout << ls.size() << endl;
list<Nodes>::iterator iter;
for(iter = ls.begin(); iter != ls.end() ;iter++)
{
cout<< iter->a <<"-"<<iter->c<<endl;
}
cout<<"迭代器只能进行自加\n";
//迭代器只能进行自加
iter++;iter++;
list<Nodes> ls2(iter,ls.end());
for_each(ls2.begin(),ls2.end(),fun);
cout<<"重置大小\n";
//重置大小
ls.resize(3);
cout << ls.size() << endl;
for_each(ls.begin(),ls.end(),fun);
cout<<"插入尾\n";
//插入尾
ls.push_back(nu1);
for_each(ls.begin(),ls.end(),fun);
cout<<"插入头\n";
//插入头
ls.push_front(nu1);
for_each(ls.begin(),ls.end(),fun);
cout<<"删除尾节点\n";
ls.pop_back();
for_each(ls.begin(),ls.end(),fun);
cout<<"删除头节点\n";
ls.pop_front();
for(iter = ls.begin(); iter != ls.end() ;iter++)
{
cout<< iter->a <<"-"<<iter->c<<endl;
}
cout<<"在二 三之间插入节点id 2 ins\n";
Nodes ins = {55,'v'};
int id =0;
for(iter = ls.begin(); iter != ls.end() ;iter++)
{
if(id == 2){
//指向新插入的iter
iter = ls.insert(iter,ins);
cout<< iter->a <<"-"<<iter->c<<endl;
break;
}
cout<< iter->a <<"-"<<iter->c<<endl;
++id;
}
cout<<"查找ins\n";
iter = find(ls.begin(),ls.end(),ins);
if(iter != ls.end())cout<<"I find it ,ins-Existent"<<endl;
else
{
cout<<"I not find it ,ins-Non-existent"<<endl;
}
Node loos={99,'y'};
iter = find(ls.begin(),ls.end(),loos);
if(iter != ls.end())cout<<"I find it ,loos-Existent"<<endl;
else
{
cout<<"I not find it ,loos-Non-existent"<<endl;
}
cout<<"删除ins\n";
ls.remove(ins);
for_each(ls.begin(),ls.end(),fun);
cout<<"清空链表 使用遍历清空,避免内存泄露"<<endl;
for(iter = ls.begin();iter != ls.end();){
//iter指向了下一个元素
iter = ls.erase(iter);
cout<<"删除中...."<<endl;
}
if(ls.empty()) cout << "empty" << endl;
else cout << "No empty" <<endl;
return 0;
}
待续...
参考文章:
https://blog.youkuaiyun.com/zichen_ziqi/article/details/80807989
https://blog.youkuaiyun.com/AAMahone/article/details/81148419
https://blog.youkuaiyun.com/zichen_ziqi/article/details/80819939