1.为什么要引入链表?
链表是线性表的一种,线性表就是n个数据元素的有限序列,可以存储若干个数据项。线性表分为链表和顺序表。顺序表一般通过数组来实现,物理和逻辑上都相邻。也就是说只要确定了线性表的起始位置,线性表中任一数据元素都可随机存取,所以顺序表的存储结构是一种随机存取的存储结构。但是顺序表的局限性在于当你要插入或删除操作时要对其他元素进行移位,这十分消耗时间。对于长度变化较大的线性表,要一次性分配足够的存储空间,但这些存储空间常常又得不到充分的利用。为了避免这些问题我们引入了链表。
2.什么是链表?
链表是由一个个结点构成的,其中每个结点包含了两部分,一部分存储数据为数据域,另一部分存储指向其直接后继结点存储位置的指针为指针域。
链表的特点在于逻辑上相邻物理上不相邻,它没有顺序表所具有的弱点,但是也失去了可随机存取的优点。也就是说当你要找到链表中的某一元素时,必须要从首个元素开始遍历直到找到。
3.头结点
在链表前第一个结点前附近设置一个结点我们称其为头结点。头结点的指针指向第一个结点,而其数据域可一不存储信息。之所以设立头结点跟头插法和删除操作有关,下面会讲到。
4.链表的实现
头插入法即为将每一个元素都插入到头结点后第一个位置,以此累积。
#include<iostream>
struct node //结点的定义
{
int data;
node * next;
};
node * create() //头插入法
{
node *head=new node;
head->next=NULL;
while(1)
{
node *p=new node;
std::cin >> p->data;
if(p->data=='#')
return head;
p->next=head->next;
head->next=p;
}
return head;
}
有以上代码可以知道头插入法的核心代码为:p->next=head->next;
head->next=p;其中head即为头结点。
尾插入法:将新的结点插入到当前链表的表位
node * create() //尾插入法
{
node *head=new node;
head->next=NULL;
while(1)
{
node *p=new node;
std::cin >> p->data;
if(p->data=='#')
return head;
p->next=head->next;
head->next=p;head=p; //相较于头插入法多了一个head=p;
}
return head;
}
5.链表的插入操作:
void insert_l(node *l,int i,int e) //在第i个结点进行插入
{
node *p=l->next;
int j=1;
while(j<i-1) //找到第i-1个结点
{
p=p->next;
j++;
}
node *s=new node;
s->data=e; //插入操作的核心代码
s->next=p->next;
p->next=s;
}
如果仔细观察就会发现插入操作的核心代码和头插法是一样的。插入操作是对第i-1个结点之后进行插入,而头插法是对头结点之后进行插入。
6.删除操作
void delete_l(node* l,int i) //对第i个结点进行删除
{
node* p=l->next;
int j=1;
while(j<i-1)
{
p=p->next;
j++;
}
p->next=p->next->next;
}
其核心代码为p->next=p->next->next;无论是插入还是删除都要知道第i-1个结点的位置。
7.单链表的合并,将两个有序单链表合并为一个有序单链表
node *merge_l(node *l1,ndoe *l2)
{
node *head=l1;
node *l3=l1;
node *p1=l1->next;
node *p2=l2->next;
while(p1&&p2)
{
if(p1->data<=p2->data)
{
l3->next=p1;
l3=p1;
p1=p1->next;
}
else
{
l3->next=p2;
l3=p2;
p2=p2->next
}
}
if(p1==nullptr) //将剩余的结点链接
l3->next=p2;
else
l3->next=p1;
delete(l2);
return head;
}