C++线性表的链式存储结构
C++实现线性表的链式存储结构:
为了解决顺序存储不足:用线性表另外一种结构-链式存储。在顺序存储结构(数组描述)中,元素的地址是由数学公式决定的,而在链式储存结构中,元素的地址是随机分布的,每个元素都有一个明确的指针指向线性表的下一个元素的位置(即地址)。
线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。在顺序结构中,每个数据元素只需要存数据元素信息就行了,而在链式结构中,除了存储数据元素信息外,还要存储它的后继元素的存储地址。所以一般结点包括两个信息:数据和指针。链表就是n个节点组成的,如果每个结点只包含一个指针,那么就是单链表。
有头有尾:我们把链表中第一个结点的存储位置叫作头指针,那么整个链表的存取就必须是从头指针开始进行的。而线性链表的最后一个结点指针为空(NULL)。从下图中可以看到,结点都是由两部分组成,数据域和指针域。
有时,为了更方便对链表进行操作,会在单链表的第一个结点前加一个头结点。头结点的数据域可以不存储任何信息,也可以存储如线性表长度等附加信息,头结点的指针域存储指向第一个结点的指针。
头指针和头结点的异同
指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针。
头指针具有标识作用,所以常用头指针冠以链表的名字。
无论链表是否为空,头指针均不为空。头指针是链表的必要元素。
(这句话真的歧义,若没有头结点,头指针head指向第一个节点,当空表时,head=NULL。应该是必须要有头指针。)
头结点
(1)头结点是为了操作的统一和方便而设立的,放在第一个元素的结点之前,其数据域一般无意义(也可存放链表的长度)
(2)有了头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其它结点的操作就统一了。
(3)头结点不一定是链表必须的要素。
链式存储结构的线性表进行的基本操作。主要包括:
- 插入:操作方式为在指定元素前插入、在指定元素之后插入、在指定位置完成插入
- 删除:操作方式可分为删除指定元素、删除指定位置的元素,删除第一个元素,删除最后一个元素
- 显示数据
- 查找:查询指定的元素(可根据某个数据成员完成查询操作)
- 定位操作:定位指定元素的序号
- 更新:修改指定元素的数据
- 数据文件的读写操作
- 计算链表的长度
定义一个结点类
/*Define a friend class to facilitate direct manipulation of data*/
template<class T>
class LinkList;
template <class T>
class LinkNode
{
friend class LinkList<T>;
private:
T _data;
LinkNode<T> *_next;
public:
LinkNode(T x )
{
_data = x;
_next = NULL;
}
};
链表的主要函数构成
template <class T>
class LinkList
{
private:
LinkNode<T> * _head;
public:
LinkList()
{_head = NULL;}
LinkNode<T>* _CreateNode(const T& x);//Create a new node
void clear(LinkNode<T>* &cur);//Delete a new node
void PushBack(const T& x);//tail insertion to create a linked list
void PushFront(const T& x);//Head insertion to create a linked list
void PopBack(); //Remove an element from the tail
void PopFront();//Remove an element from the head
int Length();//Find the length of the linear table
LinkNode<T>* Find(T x);//Find a number
void Insert_right(int pos, const T& x);//Insert after the nth
void Insert_cur(int pos, const T& x);//Insert at the specified location
void Insert_left(int pos, const T& x);//Insert in front of the nth
void Delete_pos(int pos);//Delete the nth element
void Delete_val(const T& x);//Delete specified element
void reset(const T &x,const T &y);//Update an element
int located(const T &x);//Locate the serial number of the specified element
void Print();// Print linear table
bool writeToFile();//Write file
T* readFromFile();//Read in data file
int readlen();
};
各个函数的实现
Print()函数
template<class T>
void LinkList<T>::Print()
{
LinkNode<T> *tmp = _head;
while (tmp != NULL)
{
cout << tmp->_data << "-->";
tmp = tmp->_next;
}
cout << "NULL" << endl;
}
创建一个新的结点,并为其分配空间
template <class T>
LinkNode<T>* LinkList<T>:: _CreateNode(const T& x)
{
LinkNode<T>* tmp = new LinkNode<T>(x);
return tmp;
}
清除某一个节点,释放空间
template<class T>
void LinkList<T>::clear(LinkNode<T> *&cur)
{
cur->_next = NULL;
delete cur;
cur = NULL;
}
获得链表的长度
template<class T>
int LinkList<T> ::Length()
{
int len = readlen();/*调用一个读文件的函数,来判别链表状态,Call a function that reads the file to determine the state of the linked list*/
if(len>0){
return len;
}
else {
len=0;
if (_head == NULL)
{
return 0;
}
else
{
LinkNode<T> * begin = _head;
while (begin != NULL)
{
begin = begin->_next;
len++;
}
}
}
return len;
}
前插法建立链表
从一个空表开始,重复读入数据,执行以下两步
(1)生成新的结点,将读入数据存放在新节点的的_data域中
(2)将该节点插入到链表的前端,直到读入到结束符为止。
template <class T>
void LinkList<T> :: PushFront(const T& x)
{
if (_head == NULL)
{
_head = _CreateNode(x);
}
else
{
LinkNode<T> * prev = _CreateNode(x);
prev->_next = _head;
_head = prev;
}
}
用后插法建立链表
需要设置一个尾部指针end,总是指向新链表的最后一个节点,新节点链接到它所指链尾节点的后面。end最初要置于附加头节点位置
template <class T>
void LinkList<T> ::PushBack(const T& x)
{
if (_head == NULL)
{
_head = _CreateNode(x);
}
else
{
LinkNode<T> * end = _head;
while (end->_next != NULL)
{
end = end->_next;
}
end->_next = _CreateNode(x);
}
}
从尾部删除一个数据
考虑只有一个节点情况,多个结点的情况。
多个结点,首先找到尾部元素,然后调用clear()函数,清理掉尾部第一个元素
template <class T>
void LinkList<T> :: PopBack()
{
if (_head == NULL)
{
cout << "The List is empty!!!" << endl;
return;
}
else if (_head->_next == NULL)
{
clear(_head);
}
else
{
LinkNode<T> * temp = _head;
LinkNode<T> * end = _head;
while (end->_next != NULL)
{
temp = end;
end = end->_next;
}
temp->_next = NULL;
clear(end);
}
}
从头部删除一个元素
实现方法和从尾部删除一个元素基本相似,不多加以解释
template <class T>
void LinkList<T> ::PopFront()
{
if (_head == NULL)
{
cout << "List is empty!!!" << endl;
return;
}
else if (_head ->_next == NULL)
{
clear(_head);
}
else
{
LinkNode<T> * tmp = _head;
_head = _head->_next;
clear(tmp);
tmp = NULL;
}
}
查找某一个元素
遍历整个链表,并将其数据_data与x进行比对,如果是其他类型就需要重载运算符==
template <class T>
LinkNode<T>* LinkList<T> ::Find (T x)
{
if (_head == NULL)
{
cout << "List is empty,not found!!!" << endl;
return NULL;
}
else if(_head->_data==x)
{
return _head;
}
else
{
LinkNode<T> * n = _head ;
while (n->_next != NULL && n->_data != x )
{
n = n->_next;
if (n->_data == x)
{
return n;
}
}
}
return NULL;
}
在第pos个元素后插入一个新元素
创建一个新的结点,通过移动begin指针,pos控制指针最终位置,将新元素插入到之后
template <class T>
void LinkList<T> ::Insert_right(int pos,const T& x)
{
int len = Length();
if (pos <= len)
{
if (_head == NULL)
{
cout << "List is empty!!!" << endl;
return;
}
else
{
LinkNode<T> * begin = _head;
LinkNode<T> * tmp = _CreateNode(x);
while (--pos)
{
if (begin->_next != NULL)
{
begin = begin->_next;
}
}
tmp->_next = begin->_next;
begin->_next = tmp;
}
}
else
{
cout << "Input Error!!!" << endl;
}
}
在第pos个元素插入一个新元素
因为写了一个在后面插入的函数,为了偷懒,就直接调用了Insert_right()这个函数
template <class T>
void LinkList<T> ::Insert_cur(int pos,const T& x)
{
int len = Length();
if (pos <= len)
{
if (_head == NULL)
{
cout << "List is empty!!!" << endl;
return;
}
else
{
Insert_right(pos-1, x);
}
}
else
{
cout << "Input Error!!!" << endl;
}
}
在第pos个元素前插入一个新元素
创建一个新的结点,通过移动begin指针,pos控制指针最终位置,将新元素插入到之前,与插入之后实现方法基本类似,只是需要注意pos的控制
template <class T>
void LinkList<T> ::Insert_left(int pos,const T& x)
{
int len = Length();
int temp=pos-1;
if (pos <= len)
{
if (_head == NULL)
{
cout << "List is empty!!!" << endl;
return;
}
else
{
LinkNode<T> * begin = _head;
LinkNode<T> * tmp = _CreateNode(x);
while (--temp)
{
if (begin->_next != NULL)
{
begin = begin->_next;
}
}
tmp->_next = begin->_next;
begin->_next = tmp;
}
}
else
{
cout << "Input Error!!!" << endl;
}
}
删除第pos个元素
依旧利用pos控制指针位置,然后删除就ok
template <class T>
void LinkList<T> ::Delete_pos(int pos)
{
int len = Length();
if (pos <= len)
{
if (_head == NULL)
{
cout << "List is empty!!!" << endl;
}
else if (_head->_next == NULL)
{
clear(_head);
}
else
{
LinkNode<T> * begin = _head->_next;
LinkNode<T> * temp = _head;
pos = pos - 1;
while (--pos)
{
if (begin->_next != NULL)
{
begin = begin->_next;
temp = temp->_next;
}
}
temp->_next = begin->_next;
begin->_next = temp;
}
}
else
{
cout << "Input Error!!!" << endl;
}
}
查找某一个元素位置并返回其位置
用一个temp进行计数,遍历整个链表,一一进行比对其——data数据(若是其它类型就需要重载运算符“=”,后面的函数提到比对的都需要实现,才能进行比对)比对成功就返回,其它类的实现依然需要重载
template <class T>
int LinkList<T> ::located(const T &x)
{
if (_head == NULL)
{
cout << "List is empty,not found!!!" << endl;
return -1;
}
else if(_head->_data==x)
{
return 1;
}
else
{
LinkNode<T> * n = _head ;
int temp=0;
while (n->_next != NULL && n->_data != x )
{
n = n->_next;
temp++;
if (n->_data == x)
{
return temp+1;
}
}
}
return -1;
}
删除指定的元素遍历链表
一一比对,找到就调用chear()函数删除并清理空间
template <class T>
void LinkList<T> ::Delete_val(const T &x)
{
if (_head == NULL)
{
cout << "List is empty!!!" << endl;
return;
}
else if (_head->_next == NULL && _head->_data == x)
{
clear(_head);
return;
}
else
{
if(_head->_data==x){
while(1){
if(_head->_data!=x)break;
PopFront();
}
}
else
{
while(1)
{
LinkNode<T> * n = Find(x);
if(n==NULL)break;
else{
LinkNode<T> * begin = _head;
while (begin->_next != n && begin->_next != NULL)
{
begin = begin->_next;
}
begin->_next = n->_next;
clear(n);
}
}
return ;
}
}
return;
}
更新指定元素的值
遍历链表,一一比对(前面提到了重载的),找到要更新的元素后就将新的值赋值给它
template <class T>
inline void LinkList<T>::reset(const T &x,const T &y)
{
if (_head == NULL)
{
cout << "List is empty!!!" << endl;
return ;
}
else
{
while(1)
{
LinkNode<T> * n = Find(x);
if(n==NULL)break;
else{
n->_data=y;
}
}
}
}
链表的数据写入文件
能实现所有数据的储存,但是如果是其他类,写入没有问题,如果想从同一个文件中读入之前写的数据,遇到一些困难没有实现,但是基本数据类型是ok的,测试函数就表现出来了的(其它类的读入需要重载输入流,输出流)
template <class T>
inline bool LinkList<T>::writeToFile()
{
ofstream writefile("data.txt");
int len;
len=Length();
writefile<<len<<endl;
LinkNode<T> * begin=_head;
while(begin!=NULL)
{
writefile<<begin->_data<<endl;
begin=begin->_next;
}
return true;
}
从文件读入先前写入的数据,上面有提到只能满足基本数据类型
template <class T>
inline T* LinkList<T>::readFromFile()
{
ifstream readfile("data.txt");
int length;
readfile>>length;
T *temp=new T[length];
for(int i=0;i<length;i++)
{
readfile>>temp[i];
}
return temp;
}
为了更好的操作,测试相关函数的功能,写了这样一个函数来从文件得到链表长度的函数
template <class T>
inline int LinkList<T>::readlen()
{
ifstream readfile("data.txt");
int length;
readfile>>length;
return length;
}
写到这基本上函数的功能都实现了,这个链表结构肯定有很多不完善的地方,第一次写博客,还望大家见谅。上面提到了其它类的重载问题,我就把写的一个point类分享给大家。
class Point
{
private:
double x;
double y;
public:
Point(double x=0.0,double y=0.0)
{this->x=x,this->y=y;}
void setdata(double a,double b)
{this->x=a;this->y=b;}
Point operator=(Point a);//重载运算符“=”
int operator==(Point a);//重载运算符“==”
int operator!=(Point a);
double getx()
{return x;}
double gety()
{return y;}
//重载输入流
friend istream &operator>>(istream &is,Point &p)
{
is>>p.x;
is>>p.y;
return is;
}//重载输出流
friend ostream &operator<<(ostream &os,Point p)
{
os<<"("<<p.x<<","<<p.y<<")";
return os;
}
};
inline Point Point::operator=(Point a)
{
this->x=a.x;
this->y=a.y;
return *this;
}
inline int Point::operator==(Point a)
{
if(this->x==a.getx()&&this->y==a.gety())
{
return 1;
}
else {
return 0;
}
}
inline int Point::operator!=(Point a)
{
if(this->x!=a.getx()&&this->y!=a.gety())
{
return 1;
}
else if(this->x!=a.getx()||this->y!=a.gety()){
return 1;
}
else {
return 0;
}
}
一个输入之后的展示
以上是C++顺序表使用过程中最常用的主要操作,相关完整代码已经push到GitHub,需要的小伙伴自行clone,如果觉得还不错的话,欢迎Star,这里是传送门C++链式顺序表,除此之外,想要了解更多的C,C++,Java,Python等相关知识的童鞋,欢迎来我的博客相逢的博客,我们一起讨论!
C++线性表的链式存储结构的更多相关文章
- C++编程练习(2)----“实现简单的线性表的链式存储结构“
单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素. 对于查找操作,单链表的时间复杂度为O(n). 对于插入和删除操作,单链表在确定位置后,插入和删除时间仅为O(1). 单链表不需要分配存储 ...
- 线性表的链式存储结构的实现及其应用(C/C++实现)
存档----------- #include <iostream.h> typedef char ElemType; #include "LinkList.h" voi ...
- javascript实现数据结构:线性表--线性链表(链式存储结构)
上一节中, 线性表的顺序存储结构的特点是逻辑关系上相邻的两个元素在物理位置上也相邻,因此可以随机存取表中任一元素,它的存储位置可用一个简单,直观的公式来表示.然后,另一方面来看,这个特点也造成这种存储 ...
- C 线性表的链式存储实现及插入、删除等操作示例
一.链式存储的优势 线性表的存储可以通过顺序存储或链式存储实现,其中顺序存储基于数组实现(见本人上一篇博客),在进行插入删除等操作时,需对表内某一部分元素逐个移动,效率较低.而链式结构不依赖于地址连续 ...
- typedef struct LNode命名结构指针(线性表的链式存储)
一.typedef 关键字 1. 简介: typedef工具是一个高级数据特性,利用typedef可以为某一些类型自定义名称. 2. 工作原理: 例如我们定义链表的存储结构时,需要定义结点的存储数据元 ...
- 数据结构-线性表的链式存储相关算法(C语言实现)
链表的简单介绍 为什么需要线性链表 当然是为了克服顺序表的缺点,在顺序表中,做插入和删除操作时,需要大量的移动元素,导致效率下降. 线性链表的分类 按照链接方式: 按照实现角度: 线性链表的创建和简单 ...
- 线性表 顺序存储 链式存储 ---java实现
首先抽象出一个线性表抽象类(包括主要的增删操作) public abstract class MyAbstractList<E> { public abstract void add(E ...
- 线性表的链式存储——C语言实现
SeqList.h #ifndef _WBM_LIST_H_ #define _WBM_LIST_H_ typedef void List; typedef void ListNode; //创建并且 ...
- 线性表的链式存储C语言版
#include <stdio.h> #include <malloc.h> #define N 10 typedef struct Node { int data; stru ...
随机推荐
- JSTL、请求转发和URL重定向
JSTL 为什么要使用JSTL? 因为在JSP中写JAVA代码很麻烦,而JSTL可以简化在JSp中写JAva代码的流程 如何使用JSTL? 准备工作: ①将JSTL依赖的jar包导入工程的WEB-IN ...
- iOS/macOS推荐个高效苹果开发工具, JSON 转模型代码工具,不再为复杂JSON数据写模型而烦恼,支持Swift/Objective-C,极速转换
CCJSON 是一款运行在macOS上 JSON 转模型代码工具,不再为复杂JSON数据写模型而烦恼,可识别嵌套模型,字典/数组,支持Swift/Objective-C,操作方便,极速转换.下载 效果 ...
- dfs 例题皇后问题
题目描述 一个如下的 6 \times 66×6 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行.每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子. 上面的布局可以用序列 ...
- 多线程之旅(Thread)
在上篇文章中我们已经知道了多线程是什么了,那么它到底可以干嘛呢?这里特别声明一个前面的委托没看的同学可以到上上上篇博文查看,因为多线程要经常使用到委托.源码 一.异步.同步 1.同步(在计算的理解总是 ...
- 使用docker构建双主mysql
我们有的时候需要双主mysql, 这样无论哪个数据库出现了问题的话都可以继续使用数据库.把两个数据库挂到一个代理上面,这样哪个服务出问题了,另外一个就可以继续服务了.当然本文不是主要谈代理的,所以这个 ...
- 控制台报错Cause: org.xml.sax.SAXParseException; lineNumber: 4; columnNumber: 78; 元素类型 "select" 必须后跟属性规范 ">" 或 "/>"
首先我的控制台报错是这样的,我找了一下原因看到是第四行的错误: 它说元素select后面必须跟属性规范">""/>"但是我把我眼睛都快丑瞎了都没发现 ...
- TPYBoard v202开发板通过I2C协议驱动oled
最近无聊的时候研究了一下TPYBoard v202开发板,发现网上TPYBoard开发驱动oled的这块资料比较少,本人测试成功后给大家分享一下经验 下面通过代码讲解一下 1.首先需要导包, 在网上下 ...
- Apache服务的主要目录和配置文件详解
Apache服务的主要目录和配置文件详解 2014-01-14 19:05:14 标签:httpd配置文件详解 apache配置文件 httpd配置文件 apache文件目录 原创作品,允许转载,转载 ...
- 负载均衡器nginx和ribbon区别
1,nginx 是服务器端的负载均衡器,所有请求发送到nginx之后,nginx通过反向代理的功能分发到不同的服务器,做负载均衡 2,ribbon是客户端的负载均衡器,他是通过将eureka注册中心上 ...
- P - Sudoku Killer HDU - 1426(dfs + map统计数据)
P - Sudoku Killer HDU - 1426 自从2006年3月10日至11日的首届数独世界锦标赛以后,数独这项游戏越来越受到人们的喜爱和重视. 据说,在2008北京奥运会上,会将数独列为 ...