文章目录
- 目录
- 链表的基本概念
- 1.数组和链表
- 链表的使用
- 1.链表的简单使用
- 2.链表的进阶使用
- 3.链表的高阶使用
- 4.链表的其他操作
- 链表容器list
- 1.list介绍
- 2. list使用
- 3. list与vector之间的区别
- 4.list例子代码
目录
- 数据结构:
- 逻辑结构:数组,栈,队列,字符串,树,图
- 存储结构:顺序存储,链式存储
- C++常用的数据结构有:string , stack , queue , deque , vector , list , map , iterators.
链表的基本概念
链表是一种线性表,其在内存中以链式结构存储,所以叫做链表。具有插入和删除方便,但是访问则必须从链表的头开始。
1.数组和链表
链表的使用
1.链表的简单使用
一个个节点按顺序串接起来,就构成了链表。显然这一个个节点是很关键的,假设我们要构造一个int类型的链表,那么一个节点中就需要包含两个元素:
- 一个是当前节点所保存的值,设为int value。
- 另一个就是指向下一个节点的指针,我们再假设这个节点类是node,那么这个指针就是 node *next。
这里一定不是int *next。因为这个指针指向的下一个元素是一个类的实例,而不是int类型的数据。那么node这个类最简单的实现就如下:
class node
{
public: int value; node *next; node() { value = 0; next = NULL; }
};
这个类名字为node,包含两个元素,一个是当前node的值,一个是指向下一个节点的指针,还有一个构造函数,分别将value初始化为0、next初始化为NULL
拿到这个类以后,假设我们生成两个这个类的实例,node1和node2,再将node1的next指针指向node2,就构成了有两个元素的链表。这样如果我们拿到node1这个节点,就可以通过next指针访问node2。比如下面的代码
#include <iostream>
#include <deque>using namespace std;class node{
public:int value;//链表节点的数据域node* next;//用于指向下一个节点,是链表中的指针域node():value(0),next(NULL){};//默认构造函数node(int value):value(value),next(NULL){};//带参数的构造函数 ~ node(){cout<<"delete the node!!!"<<endl;delete next; // 删除节点的指针域 }
}; int main(){node mNode1(1),mNode2(2),mNode3(3),mNode4(4);mNode1.next = &mNode2;mNode2.next = &mNode3;mNode3.next = &mNode4;cout<<"the value of node3 is :"<<mNode3.value<<endl; //直接输出节点 cout<<"the value of node3 is :"<<mNode1.next->next->value<<endl; //通过链表查找 return 0;
}
the value of node3 is :3
the value of node3 is :3
delete the node!!!
delete the node!!!
delete the node!!!
delete the node!!!
delete the node!!!
delete the node!!!
delete the node!!!
delete the node!!!
delete the node!!!
delete the node!!!
2.链表的进阶使用
上述这样就构成了一个最简单的链表,如果还有新的节点出现,那么就如法炮制,链在表尾或者表头,当然插在中间也是没问题的。
但是这样还有个问题就是node1和node2是我们提前声明好的,而且知道这两个实例的名称,如果我们需要1000甚至跟多节点,这种方式显然是不科学的,而且在很多时候,我们都是动态生成一个类的实例,返回的是这个实例的首地址。
下面的代码我们用一个for循环,生成11个节点,串起来形成一个链表
原理就是先生成一个头结点,然后动态生成10个节点,每生成一个节点,就将这个节点指向头结点,然后更新头结点为当前节点
#include <iostream>
#include <deque>using namespace std;class Node{
public:int value;//链表节点的数据域Node* next;//用于指向下一个节点,是链表中的指针域Node():value(0),next(NULL){};//默认构造函数Node(int value):value(value),next(NULL){};//带参数的构造函数 ~ Node(){cout<<"delete the node!!!"<<endl;delete next; // 删除节点的指针域 }
}; int main(){Node *head , *curr; //定义头结点指针和当前插入节点的指针head = new Node();//头插法生成链表 for(int i = 0 ; i < 10 ; i++ ){curr = new Node(i);if(curr==NULL){cerr<<"内存分配失败!"<<endl; }else{curr->next = head->next; //将当前节点指针指向之前头结点指向的地方head->next = curr; //头结点指向当前的节点cout<<"the value is : "<<curr->value<<endl;}}return 0;
}
the value is : 0
the value is : 1
the value is : 2
the value is : 3
the value is : 4
the value is : 5
the value is : 6
the value is : 7
the value is : 8
the value is : 9
那么链表该如何遍历呢,刚开头的时候就说,遍历链表需要从头到尾,访问每一个元素,直到链表尾。也就是说不断地访问当前节点的next,直到NULL。下面是链表的遍历输出
#include <iostream>using namespace std;class Node{
public:int value;//链表节点的数据域Node* next;//用于指向下一个节点,是链表中的指针域Node():value(0),next(NULL){};//默认构造函数Node(int value):value(value),next(NULL){};//带参数的构造函数 ~ Node(){cout<<"delete the node!!!"<<endl;delete next; // 删除节点的指针域 }
}; int main(){Node *head , *curr;head = new Node();//头插法生成链表 for(int i = 0 ; i < 10 ; i++ ){curr = new Node(i);if(curr==NULL){cerr<<"内存分配失败!"<<endl; }else{curr->next = head->next;head->next = curr;
// cout<<"the value is : "<<curr->value<<endl;}}//链表的遍历while(head){cout<<"current data is : "<<head->value<<endl;head = head->next;} return 0;
}
current data is : 0
current data is : 9
current data is : 8
current data is : 7
current data is : 6
current data is : 5
current data is : 4
current data is : 3
current data is : 2
current data is : 1
current data is : 0
3.链表的高阶使用
链表相对于数组有个非常明显的优点就是能以时间复杂度o(1)完成一个节点的插入或者删除操作。
插入操作的原理很简单,假设现在有三个节点,一个是当前节点curr,一个是当前节点的下一个节点,也就是后继节点,假设为next,还有一个待插入的节点,假设为insert。插入操作就是让当前节点的后继节点指向insert节点,insert节点的后继节点指向next节点。以下是示意图
删除操作的原理也是类似的,就是让当前节点的后继节点指向它后继节点的后继节点。示意图如下
那么插入和删除操作用代码如何实现呢,我们还用原先的链表,先插入一个值为20的节点,输出链表的全部元素。然后再删除链表中这个值为20的元素,输出元素的全部内容。代码如下:
#include <iostream>
#include <deque>using namespace std;class Node{
public:int value;//链表节点的数据域Node* next;//用于指向下一个节点,是链表中的指针域Node():value(0),next(NULL){};//默认构造函数Node(int value):value(value),next(NULL){};//带参数的构造函数 ~ Node(){cout<<"delete the node!!!"<<endl;delete next; // 删除节点的指针域 }
}; int main(){Node *head , *curr;head = new Node();//头插法生成链表 for(int i = 0 ; i < 10 ; i++ ){curr = new Node(i);if(curr==NULL){cerr<<"内存分配失败!"<<endl; }else{curr->next = head->next;head->next = curr;}}//链表的插入curr = head;while(curr->value != 5){ //第一步:找到要插入的节点的前驱 curr = curr->next;}cout<<"curret node is : "<<curr->value<<endl;Node* insertNode = new Node(55); //创建要插入的节点 insertNode->next = curr->next; //第二步:将插入的节点的指针域指向当前节点的后继 curr->next = insertNode; //第三步:将当前节点的指针指向插入的新节点//链表遍历curr = head;while(curr){cout<<"the value is : "<<curr->value<<endl;curr = curr->next;}//链表元素的删除//第一步找到要删除的节点的前继curr = head;while(curr->next->value != 55){curr = curr->next;}cout<<"curret node is : "<<curr->value<<endl;Node *tempNode;tempNode = curr->next; //第二步:保存下要删除的节点 curr->next = tempNode->next; //第三步:将要删除节点的前继指针指向要删除节点的后继 delete tempNode; //第四步:删除当前的节点//链表遍历curr = head;while(curr){cout<<"the value is : "<<curr->value<<endl;curr = curr->next;}return 0;
}
curret node is : 5
the value is : 0
the value is : 9
the value is : 8
the value is : 7
the value is : 6
the value is : 5
the value is : 55
the value is : 4
the value is : 3
the value is : 2
the value is : 1
the value is : 0
curret node is : 5
delete the node!!!
delete the node!!!
delete the node!!!
delete the node!!!
delete the node!!!
delete the node!!!
the value is : 0
the value is : 9
the value is : 8
the value is : 7
the value is : 6
the value is : 5
the value is : 4
the value is : 3
the value is : 2
the value is : 1
the value is : 0
至于完整的链表,STL中有标准的库,也有功能非常全面的API,只要我们知道内部的实现原理,调用这些API是非常简单的事,用起来也会得心应手。
4.链表的其他操作
// 在末尾加入新的结点
void AddToTail(ListNode** pHead, int value){ListNode* pNew = new ListNode();pNew->val = value;pNew->next = nullptr;if (*pHead == nullptr){*pHead = pNew;}else{ListNode* pNode = *pHead;while(pNode->next != nullptr)pNode = pNode->next;pNode->next = pNew;}return;
}// 删除某个值为value的结点
void RemoveNode(ListNode** pHead, int value){if(pHead == nullptr || *pHead == nullptr) return;ListNode* pToDeleted = nullptr;if((*pHead)->val == value){pToDeleted = *pHead;*pHead = (*pHead)->next;}else{ListNode* pNode = *pHead;while(pNode->next != nullptr && pNode->next->val != value)pNode = pNode->next;if(pNode->next != nullptr && pNode->next->val == value){pToDeleted = pNode->next;pNode->next = pNode->next->next;}}if(pToDeleted != nullptr){delete pToDeleted;pToDeleted = nullptr;}return;
}
链表容器list
1.list介绍
2. list使用
3. list与vector之间的区别
4.list例子代码
#include <iostream>
#include <list>using namespace std;int main(){list<int> c1;list<int>::iterator c1_iter;//向链表的末尾加入元素 c1.push_back(1);c1.push_back(2);c1.push_back(3);//使用迭代器访问链表 for(c1_iter = c1.begin();c1_iter != c1.end();c1_iter++){cout<<"data is : "<<*c1_iter<<endl;}//将链表反转 c1.reverse();for(c1_iter = c1.begin();c1_iter != c1.end();c1_iter++){cout<<"reverse data is : "<<*c1_iter<<endl;}return 0;
}
data is : 1
data is : 2
data is : 3
reverse data is : 3
reverse data is : 2
reverse data is : 1
#include <list>
#include <iostream> int main( )
{ using namespace std; list <int> c1; list <int>::iterator c1_Iter; c1.push_back( 20 ); c1.push_back( 10 ); c1.push_back( 30 ); cout << "Before sorting: c1 ="; for ( c1_Iter = c1.begin( ); c1_Iter != c1.end( ); c1_Iter++ ) cout << " " << *c1_Iter; cout << endl; c1.sort( ); cout << "After sorting c1 ="; for ( c1_Iter = c1.begin( ); c1_Iter != c1.end( ); c1_Iter++ ) cout << " " << *c1_Iter; cout << endl; c1.sort( greater<int>( ) ); cout << "After sorting with 'greater than' operation, c1 ="; for ( c1_Iter = c1.begin( ); c1_Iter != c1.end( ); c1_Iter++ ) cout << " " << *c1_Iter; cout << endl;
}
Before sorting: c1 = 20 10 30
After sorting c1 = 10 20 30
After sorting with ‘greater than’ operation, c1 = 30 20 10