说一下在链式存储中比较重要的一个概念,链式存储不仅需要存内容,还有其后继元素的地址。因此我们用一个节点Node来作为这两部分的组成数据的存储映像。存储数据元素的叫数据域,存储后继节点位置的叫指针域。
线性表的头,叫做头节点Head。头节点可以不存储任何信息,也可以存储线性表的长度等附加信息。但是必须要有指向第一个节点的指针域。(注意:头节点不算第一个节点。)
本小节主要讲的是对于单链表的操作:
- 节点的创建
- 链表的初始化
- 链表的元素索引
- 链表的删除
- 头部插入和任意位置插入
- 链表元素的输出
通过类来实现,其中节点和链表都是类。代码如下:
// 单链表的操作
// Author: Thebluewinds
// Date: 2022.07.12
#include<iostream>
using namespace std;
#include<string>
#define MAXSIZE 20
#define error 0
#define ok 0
//线性表的节点
class Node
{
public:
int data;
Node* next; //用结构体指针来指向下一个节点。
};
//创建一个单链表类
class Linklist
{
public:
Linklist(); //构造函数
~Linklist(); // 析构函数
void CreateList(int n);
void CoutLinkList();
int GetElem(int i);
int Getlenth();
void ListInsert(int i, int e);
void ListDelete(); //删除指定位置元素
void ListHeadInsert(int n); //在头部位置插入元素
private:
Node* head; //头节点指针
};
//构造单链表初始化
Linklist::Linklist()
{
head = new Node;
head->data = 0;
head->next = NULL;
}
//析构释放掉头节点
Linklist::~Linklist()
{
delete head;
}
//创建一个单链表,长度为n
void Linklist::CreateList(int n)
{
Node* p_new, *p_temp;
p_temp = head; //当前节点,头指针
if (n < 0) cout << "输入节点数有误。" << endl;
for (int i = 0; i < n; i++)
{
p_new = new Node; //新增的节点
cout << "请输入第" << i + 1 << "个数值: ";
cin >> p_new->data;
p_new->next = NULL; //新节点装入数据,并指向空指针
p_temp->next = p_new; //当前指针指向新的加入的节点
p_temp = p_new; // 新节点赋值给当前节点。
}
}
//输出单链表的所有内容
void Linklist::CoutLinkList()
{
//p指向L的第一个节点,head是头节点
if (head == NULL || head->next == NULL) cout << "链表为空。" << endl;
//新建一个节点指向head
Node* p = head;
while (p->next != NULL)
{
p = p->next; //第一次循环指向第一个节点。当前p为头节点
cout << p->data << " ";
}
cout << endl;
}
//或许单链表的长度
int Linklist::Getlenth()
{
int lenth=0;
Node* p = head;
while (p != NULL && p->next != NULL)
{
p = p->next;
lenth++;
}
return lenth;
}
//单链表的读取,找到第i位的数据元素,并返回
int Linklist::GetElem(int i)
{
int e;
int j = 1;//计数器
Node *p = head; //声明一个节点,然后让p指向头节点。
//因为不知道单链表的长度,所以不能用for循环
while (p->next !=NULL && j <= i)
{
p = p->next; //p指向下一个节点
++j;
}
e = p->data;
return e;
}
// 在i的位置上插入数据e
void Linklist::ListInsert(int i, int e)
{
//1、声明一个节点p指向链表第一个节点,初始化从1开始
//2、当j<i时,遍历链表,p指针向后移动,不断指向下一节点,j+1;
//3、若链表为空,或者j超出i的范围,说明第i个元素不存在
//4、否则查找成功,在系统中生成一个空的节点s。
//5、将e赋值给s->data
//6、单链表插入标准语句:s->next=p->next; p->next=s;
//7、成功
int j=1; //作为计数器
if (i<1 || i>Getlenth()) cout << "位置错误。" << endl;
Node* p = head;
while (j < i)
{
p = p->next;
j++;
}
//开辟一个新的节点用来存放插入的数值
Node* p_temp = new Node;
p_temp->data = e;
p_temp->next = p->next; //让s指向p的后继节点
p->next = p_temp; // 让p指向s
}
void Linklist::ListDelete()
{
int loc;
int j = 1;
if (head->next == NULL)
{
cout << "链表为空。" << endl;
}
else
{
cout << "请输入删除的元素: ";
cin >> loc;
if (loc<1 || loc>Getlenth()) cout << "输入位置错误" << endl;
Node* p = head;
while (loc > j)
{
p = p->next;
j++;
}
Node *q = p->next; //p的下一下节点,也就是待删除的节点
p->next = q->next; // p指向删除节点的后一个节点
delete q;
q = NULL;
}
}
void Linklist::ListHeadInsert(int n)
{
Node* p = head;
Node* q = new Node;
q->data = n;
q->next = p->next;
p->next = q;
}
int main() {
Linklist list1;
list1.CreateList(4); //创建单链表
list1.CoutLinkList(); //输出链表数据
int e = list1.GetElem(1); // 获取链表1位置上的数
cout<<"lenth: "<<list1.Getlenth()<<endl; // 获取链表长度,并输出
cout << "该位置上的数为:" << e << endl;
cout << "插入数据后:" << endl;
list1.ListInsert(3, 99); // 在3位置上,插入99
list1.CoutLinkList();
cout << "删除指定位置数据后:" << endl;
list1.ListDelete();
list1.CoutLinkList();
list1.ListHeadInsert(12);
list1.CoutLinkList();
system("pause");
return 0;
}
这里提一下双向链表的内容。双向链表就是在单链表的每一个结点中,再设置一个指向其前驱结点的指针域。
定义的双向链表的类如下:
class Node
{
public:
int data;
Node* next;
Node* prior;
};
双向链表对于求长度、查找元素、获取元素位置等和单链表都是几乎相同的。
下面说一个插入操作和删除操作,这两个是变动比较大的:
//双向链表插入结点p---s---p->next
s->prior = p;
s->next = p->next;
p->next->prior = s;
p->next = s;
delete p;
//双向链表的结点删除操作p->prior----p-----p->next
p->prior->next = p->next;
p->next->prior = p->prior;
好了,关于线性表的内容就先暂时看到这里,后续如果还有需要的会继续补充。然后总结一下线性表的主要知识点:
- 顺序存储结构
- 链式存储结构
- 单链表
- 静态链表
- 循环链表
- 双向链表