C++单链表的初始化,插入,删除,反转操作
2016-02-20 17:18
801 查看
链表的性质:
1.元素不能随机访问
2.链表只有一个表头
3.元素相互依赖,串联而成
链表的初始化:
链表的插入操作以及链表节点的遍历:
链表的节点删除操作
链表的倒序输出(即原来链表中各数据域存储的依次是1,2,3,4,5,6,7,8,9,10)
而倒序是将原来的头结点变成尾节点,原来的尾节点变成头结点。
倒序后将输出(10,9,8,7,6,5,4,3,2,1)
首先,链表的倒序的概念上面已经描述清楚了,首先我们应该清楚实现链表反转我们需要做哪些步骤?
1.断线(除尾节点外的所有节点都要“断线”)
2.加线(除头结点外的所有节点都要”加线“)
3.处理的节点前移一个,然后再重复1,2步骤,直到处理的节点为NULL,表示链表全部处理完毕
一开始,我们要知道的是,原来的头结点变成了尾节点,并且一开始是处理头结点。所以呢,原来的头结点的指针域应该为NULL,这样的话头结点与第一个节点便断开了,
然后便是“加线”,(线箭头的指向与原来链表的相反)加线是从第一个节点开始处理,于是我们声明一个current_node指针指向当前处理到的节点。
由于"加线"的是要将当前处理到的节点的指针域指向“断线”的节点(例如在第一个节点的“加线”操作中就是current_node -> next = head,即是第一个节点指向原来的头结点)
但是如果这样的话,当前节点便与之后的节点全部断开(原因是原来当前节点记录的是其下一个节点的地址,也就是说第一个节点记录的是第二个节点的地址),使得链表反转操作无法继续进行,所以呢,我们自然而然的就想到了再声明一个指向Node类型的next_node指针,使其先记住当前操作到的节点之后的节点的地址,即next_node = current_node -> next,这样的话,等到下一次操作的时候,直接将next_node的地址赋给current_node就好了。(表示指针前移)
然后在这期间要不断地更新头结点,因为我们可以不管链表有多长,但是呢,我们每操作一次,比如说是操作到第一个节点,使其指向头结点,那么这时候新的“头结点”就是第一个节点了,后面的话以此类推,直到操作完毕。
完整代码实现:
由于在数据结构课程中已经有学过链表的相关内容,因此其他各式各样的链表详细的基本操作便不再补充,只简要的说明一下其他链表的一些性质
1.双向链表,也叫双链表。单链表里的指针域只记录了结点的下一个结点,也就是后继结点,而双向链表的指针域同时还记录了结点的上一个结点,也就是前驱结点。有了这样的结构,我们可以从头结点遍历到尾结点,也可以从尾结点遍历到头结点了。
注意双链表与单链表的区别:
双向链表的指针域有两个指针,一个指向后继,一个指向前驱
而单链表的指针域只有一个指向后继节点的指针
2.循环链表。相比单链表,循环链表是将最后一个结点的指针指向头结点,这样使得链表更加灵活方便。循环链表里没有空指针,所以在判断结束条件时,不再是判断指针是否为空,而是判断指针是否等于某固定指针。另外,在单链表里只能访问到后面的结点,而在循环链表里可以访问到所有的结点。
注意循环链表的问题:
1.循环链表遍历时只能用结点是否等于某一结点判断结束
2.循环链表可以访问到任意结点
如有错误,还请指正,O(∩_∩)O谢谢
1.元素不能随机访问
2.链表只有一个表头
3.元素相互依赖,串联而成
链表的初始化:
#include<iostream> using namespace std; //节点类 class Node{ public: int data; //数据域 Node *next; //指向下一个节点的指针 Node(int _data){ //显式构造函数,每次对新声明的节点类的对象进行初始化 data = _data; next = NULL; } }; //链表类 class LinkList{ public: LinkList (){ head = NULL; //显式构造函数,每次对新声明的链表类进行初始化,即表示 //创建一个新的"空"链表,链表中只有头指针,并且头指针的值为NULL,表示头指针 //指向的部分也无任何数据元素 } void Insert(Node *node,int position); void Output(); private: Node *head; //设置为类的私有成员,只有通过类的成员函数才能访问 };
链表的插入操作以及链表节点的遍历:
//C++形式的链表的插入,遍历输出,链表倒序等操作
#include<iostream> using namespace std; //节点类 class Node{ public: int data; //数据域 Node *next; //指向下一个节点的指针 Node(int _data){ //显式构造函数,每次对新声明的节点类的对象进行初始化 data = _data; next = NULL; } }; //链表类 class LinkList{ public: LinkList (){ head = NULL; //显式构造函数,每次对新声明的链表类进行初始化,即表示 //创建一个新的"空"链表,链表中只有头指针,并且头指针的值为NULL,表示头指针 //指向的部分也无任何数据元素 } void Insert(Node *node,int position); void Output(); private: Node *head; //设置为类的私有成员,只有通过类的成员函数才能访问 };
void LinkList::Insert(Node *node,int position){ //position表示要插入的位置
if(head==NULL){ //在头指针为空指针的情况下,链表是空的,无任何数据元素
//包括头结点在内.因此呢,我们在插入节点的时候,如果发现头结点为空指针
head = node; //的情况,那么这就意味着我们插入的这个节点要将其设置为头结点,
return; //而后一一插入的节点则接在该节点的后方,并且通过这个节点从头开始遍历
//能够遍历到链表中所有的数据元素
}
if(position==0){ //插入的是第0个位置,为了方便,我们暂且规定头结点为第0个节点
//那么如果插入的是第0个位置的话,说明插入的节点要成为新的头结点
//而不管之前有没有头结点
node -> next = head;
head = node; //完成头结点的更新,表示头结点现在是node节点
return;
}
Node *current_node = head; //从头结点开始遍历插入的位置
int i = 0; //表示已经遍历过的节点数目
while(current_node -> next != NULL && i < position-1){
//不断寻找要插入的位置的前一个位置,然后插入在这个位置的节点与下一个位置的节点之间
current_node = current_node -> next; //不断更新current_node的值
i++;
}
if(i == position-1){ //找到了插入的位置,先更新插入节点的指针域,记录当前遍历到的位置的下一个节点
//然后再接上前面的节点
node -> next = current_node -> next;
current_node -> next = node;
}
}
void LinkList::Output(){
if(head==NULL){
return; //链表为空链表,无任何数据元素可输出
}
Node *current_node = head; //从头结点开始遍历
while(current_node!=NULL){
cout << current_node -> data << " ";
current_node = current_node -> next;
}
cout << endl;
}
int main(){
LinkList linkList;
for(int i = 1;i <= 10;i++){
Node *node = new Node(i); //用new给链表的每一个节点开辟空间
linkList.Insert(node,i-1); //调用成员函数
}
linkList.Output();
return 0;
}
链表的节点删除操作
//C++形式的链表的插入,遍历输出,链表倒序等操作 #include<iostream> using namespace std; //节点类 class Node{ public: int data; //数据域 Node *next; //指向下一个节点的指针 Node(int _data){ //显式构造函数,每次对新声明的节点类的对象进行初始化 data = _data; next = NULL; } }; //链表类 class LinkList{ public: LinkList (){ head = NULL; //显式构造函数,每次对新声明的链表类进行初始化,即表示 //创建一个新的"空"链表,链表中只有头指针,并且头指针的值为NULL,表示头指针 //指向的部分也无任何数据元素 } void Insert(Node *node,int position); void Output(); void delete_node(int delete_position); private: Node *head; //设置为类的私有成员,只有通过类的成员函数才能访问 }; void LinkList::Insert(Node *node,int position){ //position表示要插入的位置 if(head==NULL){ //在头指针为空指针的情况下,链表是空的,无任何数据元素 //包括头结点在内.因此呢,我们在插入节点的时候,如果发现头结点为空指针 head = node; //的情况,那么这就意味着我们插入的这个节点要将其设置为头结点, return; //而后一一插入的节点则接在该节点的后方,并且通过这个节点从头开始遍历 //能够遍历到链表中所有的数据元素 } if(position==0){ //插入的是第0个位置,为了方便,我们暂且规定头结点为第0个节点 //那么如果插入的是第0个位置的话,说明插入的节点要成为新的头结点 //而不管之前有没有头结点 node -> next = head; head = node; //完成头结点的更新,表示头结点现在是node节点 return; } Node *current_node = head; //从头结点开始遍历插入的位置 int i = 0; //表示已经遍历过的节点数目 while(current_node -> next != NULL && i < position-1){ //不断寻找要插入的位置的前一个位置,然后插入在这个位置的节点与下一个位置的节点之间 current_node = current_node -> next; //不断更新current_node的值 i++; } if(i == position-1){ //找到了插入的位置,先更新插入节点的指针域,记录当前遍历到的位置的下一个节点 //然后再接上前面的节点 node -> next = current_node -> next; current_node -> next = node; } } void LinkList::Output(){ if(head==NULL){ return; //链表为空链表,无任何数据元素可输出 } Node *current_node = head; //从头结点开始遍历 while(current_node!=NULL){ cout << current_node -> data << " "; current_node = current_node -> next; } cout << endl; } void LinkList::delete_node(int delete_position){ if(head==NULL){ return; //链表为空链表,无任何数据元素可删除 } Node *current_node = head; //从头结点开始遍历删除的位置 int i = 0; //表示已经遍历过的节点数目 if(delete_position==0){ head = head->next; //删除的是第0个节点,即删除的是头结点,那么新的头结点即为 //第一个节点 delete current_node; //删除原来头结点申请的内存,防止内存泄漏 return; } while(current_node -> next != NULL && i < delete_position-1){ //不断寻找要删除的位置的前一个位置,然后插入在这个位置的节点与下一个位置的节点之间 current_node = current_node -> next; //不断更新current_node的值 i++; } if(i == delete_position-1){ //找到了删除的位置 Node *delete_node = current_node -> next; //delete_node用于记住需要删除的节点的地址 current_node -> next = current_node -> next -> next; delete delete_node; //同样是将申请的内存归还给系统,防止内存泄漏 } } int main(){ LinkList linkList; for(int i = 1;i <= 10;i++){ Node *node = new Node(i); //用new给链表的每一个节点开辟空间 linkList.Insert(node,i-1); //调用成员函数 } linkList.Output(); linkList.delete_node(9); //总共10个节点,i==1时为头结点,后九个节点一一接在头结点之后 linkList.Output(); return 0; }
链表的倒序输出(即原来链表中各数据域存储的依次是1,2,3,4,5,6,7,8,9,10)
而倒序是将原来的头结点变成尾节点,原来的尾节点变成头结点。
倒序后将输出(10,9,8,7,6,5,4,3,2,1)
首先,链表的倒序的概念上面已经描述清楚了,首先我们应该清楚实现链表反转我们需要做哪些步骤?
1.断线(除尾节点外的所有节点都要“断线”)
2.加线(除头结点外的所有节点都要”加线“)
3.处理的节点前移一个,然后再重复1,2步骤,直到处理的节点为NULL,表示链表全部处理完毕
一开始,我们要知道的是,原来的头结点变成了尾节点,并且一开始是处理头结点。所以呢,原来的头结点的指针域应该为NULL,这样的话头结点与第一个节点便断开了,
然后便是“加线”,(线箭头的指向与原来链表的相反)加线是从第一个节点开始处理,于是我们声明一个current_node指针指向当前处理到的节点。
由于"加线"的是要将当前处理到的节点的指针域指向“断线”的节点(例如在第一个节点的“加线”操作中就是current_node -> next = head,即是第一个节点指向原来的头结点)
但是如果这样的话,当前节点便与之后的节点全部断开(原因是原来当前节点记录的是其下一个节点的地址,也就是说第一个节点记录的是第二个节点的地址),使得链表反转操作无法继续进行,所以呢,我们自然而然的就想到了再声明一个指向Node类型的next_node指针,使其先记住当前操作到的节点之后的节点的地址,即next_node = current_node -> next,这样的话,等到下一次操作的时候,直接将next_node的地址赋给current_node就好了。(表示指针前移)
然后在这期间要不断地更新头结点,因为我们可以不管链表有多长,但是呢,我们每操作一次,比如说是操作到第一个节点,使其指向头结点,那么这时候新的“头结点”就是第一个节点了,后面的话以此类推,直到操作完毕。
完整代码实现:
//C++形式的链表的插入,遍历输出,链表倒序等操作 #include<iostream> #include<cstdlib> using namespace std; //节点类 class Node{ public: int data; //数据域 Node *next; //指向下一个节点的指针 Node(int _data){ //显式构造函数,每次对新声明的节点类的对象进行初始化 data = _data; next = NULL; } }; //链表类 class LinkList{ public: LinkList (){ head = NULL; //显式构造函数,每次对新声明的链表类进行初始化,即表示 //创建一个新的"空"链表,链表中只有头指针,并且头指针的值为NULL,表示头指针 //指向的部分也无任何数据元素 } void Insert(Node *node,int position); void Output(); void Reverse_order(); private: Node *head; //设置为类的私有成员,只有通过类的成员函数才能访问 }; void LinkList::Insert(Node *node,int position){ //position表示要插入的位置 if(head==NULL){ //在头指针为空指针的情况下,链表是空的,无任何数据元素 //包括头结点在内.因此呢,我们在插入节点的时候,如果发现头结点为空指针 head = node; //的情况,那么这就意味着我们插入的这个节点要将其设置为头结点, return; //而后一一插入的节点则接在该节点的后方,并且通过这个节点从头开始遍历 //能够遍历到链表中所有的数据元素 } if(position==0){ //插入的是第0个位置,为了方便,我们暂且规定头结点为第0个节点 //那么如果插入的是第0个位置的话,说明插入的节点要成为新的头结点 //而不管之前有没有头结点 node -> next = head; head = node; //完成头结点的更新,表示头结点现在是node节点 return; } Node *current_node = head; //从头结点开始遍历插入的位置 int i = 0; //表示已经遍历过的节点数目 while(current_node -> next != NULL && i < position-1){ //不断寻找要插入的位置的前一个位置,然后插入在这个位置的节点与下一个位置的节点之间 current_node = current_node -> next; //不断更新current_node的值 i++; } if(i == position-1){ //找到了插入的位置,先更新插入节点的指针域,记录当前遍历到的位置的下一个节点 //然后再接上前面的节点 node -> next = current_node -> next; current_node -> next = node; } } void LinkList::Output(){ if(head==NULL){ return; //链表为空链表,无任何数据元素可输出 } Node *current_node = head; //从头结点开始遍历 while(current_node!=NULL){ cout << current_node -> data << " "; current_node = current_node -> next; } cout << endl; } void LinkList::Reverse_order(){ if(head==NULL){ return; //链表为空链表 } Node *current_node = head -> next,*next_node; //从头结点开始遍历 head -> next = NULL; while(current_node != NULL){ next_node = current_node -> next; //记住当前操作节点的后面节点的地址 current_node -> next = head; head = current_node; current_node = next_node; } } int main(){ LinkList linkList; for(int i = 1;i <= 10;i++){ Node *node = new Node(i); //用new给链表的每一个节点开辟空间 linkList.Insert(node,i-1); //调用成员函数 } linkList.Output(); linkList.Reverse_order(); linkList.Output(); return 0; }
由于在数据结构课程中已经有学过链表的相关内容,因此其他各式各样的链表详细的基本操作便不再补充,只简要的说明一下其他链表的一些性质
1.双向链表,也叫双链表。单链表里的指针域只记录了结点的下一个结点,也就是后继结点,而双向链表的指针域同时还记录了结点的上一个结点,也就是前驱结点。有了这样的结构,我们可以从头结点遍历到尾结点,也可以从尾结点遍历到头结点了。
注意双链表与单链表的区别:
双向链表的指针域有两个指针,一个指向后继,一个指向前驱
而单链表的指针域只有一个指向后继节点的指针
2.循环链表。相比单链表,循环链表是将最后一个结点的指针指向头结点,这样使得链表更加灵活方便。循环链表里没有空指针,所以在判断结束条件时,不再是判断指针是否为空,而是判断指针是否等于某固定指针。另外,在单链表里只能访问到后面的结点,而在循环链表里可以访问到所有的结点。
注意循环链表的问题:
1.循环链表遍历时只能用结点是否等于某一结点判断结束
2.循环链表可以访问到任意结点
如有错误,还请指正,O(∩_∩)O谢谢
相关文章推荐
- C++ "#"的作用和用法
- utilities(C++)——Null类的设计
- C++中map的使用详解说明
- c++异常捕获
- C/C++内存问题检查利器
- c++笔试题总结1
- C++内存管理
- c++中的栈和队列
- [C++]虚函数与纯虚函数
- C语言之实现随机数产生算法
- C语言之实现随机数产生算法
- C语言之实现随机数产生算法
- 为什么C++编译器不能支持对模板的分离式编译
- servlet中Java调用C++,JNI
- C++语言文件的定义与操作
- 1020. Tree Traversals (25)
- C语言union 关键字与大小端模式
- 【C语言】文件常用读写操作(含读取学生信息示例)
- [UVA1368]DNA Consensus String
- c语言入门之项目2.3——利息计算器