目录
1,单链表的基本概念与特点
单链表也叫作线性链表,是线性表的链式存储表示,,使用单链表进行存储,各个元素可以相继存储,也可以不相继存储,不过它为每个元素附加了一个指针,并且形成一个一个的节点。通过节点中的链接指针,把各个元素按照其逻辑顺序依次连接起来。因此,每个节点中包含两个域,一个指针域一个数据域,分别是next和data两个域。data用来存放各个节点中的数据,而next用来记录该链表中下一个节点开始存储地址的指针。链表的第一个节点可以通过头指针First找到,其他节点的地址则在其前驱节点的next域中,链表的最后一个节点没有后继,所以再其next域中存放空指针,因此在单链表中访问中间节点,必须通过头结点依次寻找访问。如图即是一个单链表。
2, 单链表的特点
(1)单链表中的数据元素的逻辑顺序可能不一致,一般通过单链表指针将各个元素按照线性表的逻辑顺序链接起来。
(2)单链表的长度可以扩充,对单链表的遍历或者查找只能从头指针的首元节点开始,跟随链接指针逐个进行访问,不能如同顺序表那样直接访问某一个节点。
(3)当进行删除或者插入运算时,只需修改相关节点的指针域即可。
(4)由于链表的每个节点多了一个指针域,因此比起顺序表需要的空间也更多。
3,单链表的结构定义及其方法的实现
3.1,单链表结构的定义
//单链表节点定义
typedef int DataType;
typedef struct node
{
DataType data;//数据域
struct node *next;//指针域
}LinkNode,*LinkList;
3.2,方法的基本实现
在这里有一个小小的注意点,我们在建立单链表的时候,常常为了删除或者插入算法的实现方便一点,常常在链表的头加上一个头结点,目的也就是让插入或者删除操作算法统一。如果我们不加头结点,那么在删除第一个节点或者在第一个节点前面插入一个元素时,往往要在这里进行特殊处理,如果加上头结点,则就避免了不必要的麻烦,简化了删除插入操作,所以在建立单链表的时候加上一个头结点是很有必要的。(如下图就是带头结点的单链表空和非空时的状态)
//初始化单链表,建立只有头结点的空链表
void initlist(LinkList &list)
{
list = (LinkNode*)malloc(sizeof(LinkNode));//创建一个头结点
list->next= nullptr;//置空操作
}
//清空单链表,仅仅保留单链表的头结点
void clearList(LinkList &list)
{
LinkNode *q;
while (list->next != nullptr)
{
q = list->next;
list->next = q->next;
free(q);
}
}
//计算单链表的长度,返回单链表的长度
int get_LinkList_length(LinkList &list)
{
int count = 0;
LinkNode *q = list->next;
while (q != nullptr)
{
q = q->next;
count++;
}
return count;
}
//在单链表中查找x匹配的元素,返回该节点的地址
LinkNode *Linklist_Search(LinkList &list,DataType x)
{
LinkNode *p = list->next;
while (p != nullptr&&p->data != x)
p = p->next;
return p;
}
//对链表第i个节点定位,成功返回第i个节点的地址
LinkNode *Locate(LinkList &list, DataType i)
{
if (i < 0)
return NULL;
LinkNode *p = list;
int k = 0;
while (p != NULL && k < i)//循环查找第i个节点,k作为节点计数
{
p = p->next;
k++;
}
return p;
}
//将新元素x插入到第i个节点,带头结点的单链表的插入算法
bool inSert(LinkList &list, int i, DataType x)
{
LinkNode *p = Locate(list, i - 1);
if (p == NULL)
return false;
LinkNode *newNode = (LinkNode*)malloc(sizeof(LinkNode));//建立新节点
newNode->data = x;
newNode->next = p->next;
p->next = newNode;
return true;
}
//删除单链表第i个元素,并用x返回其值,带头节点的单链表的删除算法
bool Remove(LinkList &list, int i, DataType x)
{
LinkNode *p = Locate(list, i - 1);//定位到第i-1个元素
if (p == nullptr&&p->next == nullptr)
return false;
LinkNode *q = p->next;//q保存被删除节点的节点
p->next = q->next;//p指向第i+1个节点
x = q->data;
free(q);
return true;
}
//输出带头节点的单链表
void printList(LinkList &list)
{
LinkNode *p;
for (p = list->next; p != nullptr; p = p->next)
printf("%d", p->data);
printf("\n");
}
3.3,单链表的插入删除操作讲解
注:上面两个单链表的删除插入算法是带头节点的,在这里分析的删除或者插入算法是不带头结点的算法,基本思路都是一样的,只是为了让大家分清楚带头结点的单链表优势在哪里,所以在这里实现了不带头结点的删除插入算法,本人在学习这里时也犯了很多错误,所以在这里把删除与插入算法做详细分析。
单链表的插入有三种情况,如果在非空单链表的第i个位置插入一个新元素,分别讨论如下:
(1)若i=1,则新插入节点newNode在首元结点前,这时必须修改链表的头指针first,插入时要修改指针为:
newNode->next=first,first=newNode
(2)若果1<i<=n,则插入位置在中间,此时首先让一个辅助节点p指向第i-1个位置,在吧新节点newNode插入p节点所指的后一个节点,新节点的后面连接到原来的第i个节点。此时需要修改的指针为:
newNode->next=p->next,p->next=newNode
(3)当i>n时,新节点应追加在表尾,这时先令检测指针p指向表的最后一个节点。修改指针为:
newNode->next=p->next,p->next=newNode
//将新元素x插入到链表第i个位置,如果i给的太大,则直接插入到链表尾
bool inSert(LinkList &list, int i, DataType x)
{
if (list == nullptr || i == 1)//插入到空链表或者非空表首元节点前
{
LinkNode *newNode = (LinkNode*)malloc(sizeof(LinkNode));//建立新节点
newNode->data = x;
newNode->next = list;//新节点成为首元
list = newNode;
}
else//插入到链表中间或者尾部
{
LinkNode *p = list, *pr;
int k = 1;
while (p != nullptr&&k < i - 1)//从首元节点开始检测,寻找第i-1个节点
{
pr = p;
p = p->next;
k++;
}
if (p == nullptr&&list != nullptr)
p = pr;//链表太短,即i太大,p指向表尾
LinkNode *newNode = (LinkNode*)malloc(sizeof(LinkNode));
newNode->data = x;
newNode->next = p->next;//插入到p节点后面
p->next = newNode;
}
return true;
}
3.4,单链表的删除算法
单链表的删除算法有三种情况:
(1)删除链表的首元节点,这时首先用指针q保存首源节点的地址,再将链表的头指针first指向其下一个节点,使其成为新链表的首元节点,然后在删除q保存的原首元节点,修改指针为:
q=list,list=list-next;free(q)
(2)在链表结尾或者中间删除节点,先用指针q保存第i个节点的地址,再让第i-1个指针的next域指向第i+1个节点的地址,最后删除节点q保存节点的地址。(p指向第i-1个节点)修改指针如下:
q=p->next,p->next=q->next;free(q);
//删除单链表第i个元素,并用x返回其值
bool Remove(LinkList &list, int i, DataType x)
{
LinkNode *p, *q;
int k;
if (i == 0)
return false;
else
if (i == 1)//删除首元节点表头退到下一个节点
{
q = list;
list = list->next;
}
else
{
p = list;
k = 1;
while (p != nullptr&&k < i - 1)//循环找到第i-1个节点
{
p = p->next;
k++;
}
if (p ==NULL && p->next == nullptr)//空表或者链表太短,即i值太大
{
return false;
}
q = p->next;//保存第i个节点的地址
p->next = q->next;
}
x = q->data;
free(q);
return true;
}
3.5,单链表的顺序访问与尾递归
所谓尾递归是是指在递归程序中只有一个递归语句并且它处于程序的最后,应为单链表只可以顺序访问并且属于递归的线性结构,所以基于单链表的许多算法都可以用递归实现。
//在以first为头结点的单链表中查找其值等于给定的x节点
LinkNode *Search(LinkNode *first, DataType x)
{
if (first == NULL)
return nullptr;
else
if (first->data == x)
return first;
else
return Search(first->next, x);
}
//调用方式
LinkNode *p=Search(first->next,x)
3.6,尾插法建立单链表(递归方法实现)
所谓尾插法是指每次新节点都插到链表的表尾,这样插入的结果是各个节点中的数据的逻辑顺序与输入数据的顺序一致。
从一个空表开始,重复读取数字,执行两步操作:
(1)生成新节点,将读取的数字存入到data域中
(2)将该节点插入到链表的表尾,直到读入结束字符为止。
//尾递归发建立单链表,程序中设置了一个尾指针last,它总是指向新链表中的最后一个节点,
//新节点连接到它所指尾节点的后面,last设定为引用性指针,他要把新建的节点地址或空地址传到前一个节点next域中。
#include<stdio.h>
void insertRear(LinkNode *&last, DataType endTag)
{
//endtag是约定输入序列的结束标志,可以任意设定
DataType val;
scanf("%d", &val);
if (val == endTag)
last = NULL;
else
{
last = (LinkNode*)malloc(sizeof(LinkNode));//建立新节点
last->data = val;
insertRear(last->next, endTag);
}
}
int main()
{
LinkList l;
initlist(l);
DataType endTag;
scanf("%d", &endTag);
LinkNode *rear = l;
insertRear(rear->next, endTag);
printList(l);
}
3.7,头插法建立单链表(递归实现)
所谓头插法是指每次插入的新节点总是作为首元节点插在链表的头结点之后,插入的结果是使得链表中各节点中的数据的逻辑顺序与输入数据的顺序刚好相反。
#include<stdio.h>
void insertFront(LinkNode *&first, DataType endTag)
{
//endtag是约定输入序列的结束标志,可以任意设定
DataType val;
scanf("%d", &val);
if (val == endTag)
return;
LinkNode *p = (LinkNode*)malloc(sizeof(LinkNode));//建立新节点
p->next = first->next;
first->next = p;
insertFront(first, endTag);
}
int main()
{
LinkList l;
initlist(l);
DataType endTag;//约定输入结束标志
scanf("%d", &endTag);
LinkNode *rear = l;
insertFront(l, endTag);
printList(l);
}
3.8,非递归算法实现链表的头插法和尾插法
//尾插法
void insertRear(LinkList &list)
{
//对一个单链表list,已初始化,从空链表开始,用尾插法建立单链表
LinkNode *newNode, *last;
DataType val, endTag;
last = list;
scanf("%d", endTag);//约定结束标志
while (val != endTag)
{
newNode= (LinkNode*)malloc(sizeof(LinkNode));//建立新节点
newNode->data = val;
last->next = newNode;
last = newNode;
scanf("%d", val);
}
last->next = nullptr;
}
//头插法
void insertFront(LinkList &list)
{
//对一个单链表list,已初始化,从空链表开始,用头插法建立单链表
LinkNode *newNode, *last;
DataType val, endTag;
last = list;
scanf("%d", endTag);
while (val != endTag)
{
newNode = (LinkNode*)malloc(sizeof(LinkNode));//建立新节点
newNode->data = val;
newNode->next = list->next;//连接到头结点之后
list->next = newNode;
scanf("%d", val);
}
}