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 ...
随机推荐
- XiaoQi.Study 项目(三)
一.配置跨域 1.首先注册跨域要求 ,(可访问的IP.端口) //注册跨域 services.AddCors(options => { options.AddPolicy("XiaoQ ...
- 通过极简模拟框架让你了解ASP.NET Core MVC框架的设计与实现[上篇]
<200行代码,7个对象--让你了解ASP.NET Core框架的本质>让很多读者对ASP.NET Core管道有了真实的了解.在过去很长一段时间中,有很多人私信给我:能否按照相同的方式分 ...
- 在linux系统中安装LANMP
1.安装LANMP步骤 root@kali:~# wget http://dl.wdlinux.cn/files/lanmp_v3.tar.gz #下载 root@kali:~# tar xzvf l ...
- 什么是EIP、ESP、EBP
堆栈是一种简单的数据结构,是一种只允许在其一端进行插入或删除的线性表.允许插入或删除操作的一端称为栈顶,另一端称为栈底,对堆栈的插入和删除操作被称入栈和出栈.有一组CPU指令可以实现对进程的内存实现堆 ...
- Unity 游戏框架:UI 管理神器 UI Kit
UI Kit 快速入门 首先我们来进行 UI Kit 的快速入门 制作一个界面的,步骤如下: 准备 生成代码 逻辑编写 运行 1. 准备 先创建一个场景 TestUIHomePanel. 删除 Hie ...
- 【视频+图文】Java经典基础练习题(三):输入3个整数,并将其由小到大输出
目录 一.视频讲解 二.思路分析 总结: 三.代码+详解+结果 四.彩蛋 能解决题目的代码并不是一次就可以写好的 我们需要根据我们的思路写出后通过debug模式找到不足再进行更改 多次测试后才可得到能 ...
- 使命召唤:战区国际服ID注册与登录
命召唤:战区 国际服ID注册与登录 1.下面官网网页注册国际服账号.2登录游戏.就这么简单.(前提是网咖.电竞宾馆.已经提供好游戏) !!!注意 如果是网吧网咖电竞宾馆,用其给你提供的游戏图标进入游 ...
- Hive常用的10个系统函数及作用
聚合函数 函数处理的数据粒度为多条记录. sum()—求和 count()—求数据量 avg()—求平均直 distinct—求不同值数 min—求最小值 max—求最人值 分析函数 Analytic ...
- 李宏毅老师机器学习课程笔记_ML Lecture 3-1: Gradient Descent
引言: 这个系列的笔记是台大李宏毅老师机器学习的课程笔记 视频链接(bilibili):李宏毅机器学习(2017) 另外已经有有心的同学做了速记并更新在github上:李宏毅机器学习笔记(LeeML- ...
- effective-java学习笔记---优先使用泛型方法30
泛型类型比需要在客户端代码中强制转换的类型更安全,更易于使用. 当你设计新的类型时,确保它们可以在没有这种强制转换的情况下使用. 这通常意味着使类型泛型化. 如果你有任何现有的类型,应该是泛型的但实际 ...