线性表的链式实现:链表
简介:
在线性表中插入,删除元素时需要移动很多元素,如果数据元素本身很大,就比较耗时。另外,如果线性表的元素个数非常多,可能无法申请到一个足够大的内存块,为了克服这些缺点,可以考虑用所谓的链表来表示线性表。链表在插入,删除上比顺序表更为省时,但是在查找上效率则不如顺序表,下面简单介绍一下链表以及链表的一些基本操作
所有代码:
#include<iostream>
//这里实现几个基本的线性表操作
//1.初始化操作
//2.检测线性表是否为空
//3.将线性表清空
//4.将线性表中第i个元素的位置返回
//5.在线性表查找元素
//6.插入
//7.删除
//8.返回线性表中的元素个数
using namespace std;
//头结点的作用:让第一个元素节点也有前驱,使得其和其他节点一致,简化了代码的操作
//然后定义表示整个链表的类List,将LNode作为其内部嵌套类
class List
{
//定义一个数据元素的节点类型
struct LNode//struct定义的类的成员默认是public的
{
char data;
LNode* next = nullptr;//next是指向下一个元素结点的指针变量
};
LNode* head;//指向头结点的指针变量
public:
List() :head(new LNode()){};//初始化一个不含任何数据只有头结点的空链表
void clear();//清空线性表
bool ListEmpty();//判断链表是否为空
bool insert(const int i, const char& e);//插入元素操作(在i处插入)
bool erase(const int i);//删除元素操作(删除i处元素)
bool push_front(const char& e);//在头部插入元素
bool pop_front();//在头部删除元素
bool get(const int i, char& e)const;//读取i元素
bool set(const int i, const char e);//修改i元素
int size()const;//查询表长
//定义一个locate函数定义序号是i的数据元素
LNode* locate(const int i)const;
};
//单链表的清空算法思路:
//1.声明结点p和q
//2.将第一个结点赋值给p
//3.循环:(1)将下一结点赋值给q (2)释放p (3)将q赋值给p
void List::clear()
{
LNode* p;
LNode* q;
p = head->next;//指向头结点的下一个结点,即第一个结点
while (p != NULL)
{
q = p->next;
delete p;
p = q;
}
head->next = NULL;//最后将头结点的指针域置空
}
bool List::ListEmpty()
{
if (head->next==NULL)//头结点指向为空
{
return true;
}
return false;
}
//这里我们定义一个locate成员函数用于定位序号是i的数据元素
List::LNode* List::locate(const int i)const
{
if(i<0)//插入位置不合法
{
return nullptr;
}
LNode* p = head;
int j = 0;//计数器j为0
while (p&&j<i)//当p不指向链表尾部时,一直循环
{
p = p->next;
j++;
}
return p;
//如果返回的指针是空指针,说明没找到;如果非空,则p指向序号为i的结点
}
//插入元素操作(在i处插入)
bool List::insert(const int i, const char& e)
{
LNode* p = locate(i - 1);
if (p)
{
LNode* q = new LNode;
if (!q)//q为空
{
return false;
}
q->data = e;
q->next = p->next;
p->next = q;
return true;
}
return false;
}
bool List::erase(const int i)
{
LNode* p = locate(i - 1);
//删除操作,这里我们使用q保存要删除的结点地址,p是q的前驱
if (p)
{
LNode* q = p->next;
p->next = q->next;
delete q;
return true;
}
return false;
}
//在头部插入元素
bool List::push_front(const char &e)
{
LNode* p = new LNode;//创建一个新的结点
if (!p)//如果p是空指针,内存分配失败
{
return false;
}
p->data = e;//将新数据放入p的指针域中
p->next = head->next;
head->next = p;
}
//在头部删除元素
bool List::pop_front()
{
//首先检测是否是空表
if (!head->next)//空表的条件?
{
return false;
}
LNode* p = head->next;//p指向要删除的首结点
head->next = p->next;//绕过首结点
delete p;//删除原来的首结点
return true;
}
//查询表长
//从头结点走到尾结点,看看遇到了多少数据结点
int List::size()const
{
LNode* p(head);
int i = 0;//i是一个计数器
p = p->next;
while (p)
{
i++;
p = p->next;
}
return i;
}
//读取i元素
bool List::get(const int i,char& e)const
{
LNode* p = locate(i);
if (p)
{
e = p->data;
return true;
}
return false;
}
//修改i元素
bool List::set(const int i, const char e)
{
LNode* p = locate(i);
if (p)
{
p->data = e;
return true;
}
return false;
}
//定义一个打印函数
void print(const List&l)
{
char e;
for (int i = 0; i <=l.size(); i++)
{
l.get(i, e);
cout << e << '\t';
}
}
int main()
{
List l;
//注意,头插法的元素顺序和插入顺序相反
l.push_front('a');
l.push_front('b');
l.push_front('c');
print(l);
//注意,在vector容器中i表示插入元素的下标,而这里i则直接表示插入元素的位置
l.insert(1, 'd');
print(l);
char e;
l.get(1, e);
cout << e << endl;
l.erase(1);
print(l);
l.set(1, 'z');
print(l);
}
//问题:尾插法以及尾指针的使用方式
代码详解:
1.链表的初始化:
源代码中使用了构造函数的初始化列表来初始化链表
List() :head(new LNode()){};//初始化一个不含任何数据只有头结点的空链表
当然也可以定义一个尾结点,这里先不做解释
2.清空单链表:
单链表的清空算法思路:
1.声明结点p和q
2.将第一个结点赋值给p
3.循环:(1)将下一结点赋值给q (2)释放p (3)将q赋值给p
void List::clear()
{
LNode* p;
LNode* q;
p = head->next;//指向头结点的下一个结点,即第一个结点
while (p != NULL)
{
q = p->next;
delete p;
p = q;
}
head->next = NULL;//最后将头结点的指针域置空
}
3.单链表的插入:
bool List::insert(const int i, const char& e)
{
LNode* p = locate(i - 1);
if (p)
{
LNode* q = new LNode;
if (!q)//q为空
{
return false;
}
q->data = e;
q->next = p->next;
p->next = q;
return true;
}
return false;
}
4.单链表的删除
bool List::erase(const int i)
{
LNode* p = locate(i - 1);
//删除操作,这里我们使用q保存要删除的结点地址,p是q的前驱
if (p)
{
LNode* q = p->next;
p->next = q->next;
delete q;
return true;
}
return false;
}