目录
大家好,继上次学习了线性表的顺序存储结构,我们今天来学习线性表的链式存储结构。它有三种结构:单链表,循环链表,双向链表。今天让我们来学习剩下的知识。
与顺序存储不同的是,链式存储是用多少就向内存申请开辟多少空间,这样可以避免浪费。他们的内存地址不是连续的,链表的结点是由数据域(data)和指针域(next)两部分组成,数据域用来存储结点的值,指针域用来存储数据元素的直接后继的地址的。因为第一个结点没有前驱结点,所以首先要设一个头指针指向第一个结点,每当新申请一个结点,就把新结点的指针存到前驱结点的指针域,最后一个结点没有直接后继,所以最后一个结点的指针域设为“空”(NULL)
头指针是整个链表的开始,所以我们用头指针来表示整个链表,假如头指针为H,我们就可以从头指针开始,顺着每个结点的next,我们可以找到链表中的每一个元素。
单链表
单链表初始化
首先先创建一个结构体作为链表的结点。
用malloc函数创建一个头结点。 我们就完成了单链表的初始化,在这之后我们就可以用过头插法或者尾插法来建立单链表啦。
头插法建立单链表
头插法顾名思义就是在链表的头部插入新结点,但是由于是在头部插入,所以建立的单链表的逻辑顺序与输入元素相反,所以头插法建立的链表为逆序。具体操作为:
1.将要插入的值赋给新结点的数据域
2.将头指针的指针域中的地址赋给新结点的指针域。
3.将头结点与新结点连接。
下面的代码段保证你一读就懂。
void headpush(linck* head)
{
char c;
linck* L=head;
int flag = 1;//设立一个标志来控制插入操作的进行与停止
while (flag)
{
c = getchar();
if (c != '^')//当c=getchar不是‘^’就进行插入操作
{
linck* s = (linck*)malloc(sizeof(linck));
s->date = c;//给新结点数据域赋值
s->next = L->next;
L->next = s;
}
else
flag = 0;//当c=‘^’就重置flag的值停止插入操作
}
}
尾插法建立单链表
尾插法的名字也很通俗易懂,将新结点插入到链表的尾部,这样建立的链表逻辑与输入元素相同。具体操作为:
1.将要插入的值赋给新结点的数据域
2.将链表尾部结点的指针域指向新结点
3.将尾部指针向后移动指向新结点
void endpush(linck* head)
{
linck* L = head;
int flag = 1;
char c;
while (flag)
{
c = getchar();
if (c != '^')
{
linck* s = (linck*)malloc(sizeof(linck));
s->date = c;
L->next = s;
L = s;
}
else
{
flag = 0;
L->next = NULL;
}
}
}
单链表的查找
单链表的查找分为两种——1.按序号查找 2.按值查找。
他们两个方法大同小异所以我就只举例按序号查找,我相信你们可以自己写出按值查找的代码的。
按序号查找时我们要将链表的头指针和要查找的结点序号传入查找函数。从头结点开始扫描链表,直到找到要查找的结点序号,就返回该结点的指针。代码如下
linck* getdate(linck* head, int i)
{
linck* L = head;
int j=0;//设置计数器
if (i <= 0)
return NULL;
while (L->next != NULL && (j < i))
{
L = L->next;//扫描下一个结点
j++;//计数器加一
}
if (j == i)
return L;//找到了返回结点地址
else
return NULL;
}
求单链表长度
求链表长度其实很简单,设置一个计数器,然后从头开始扫描,每扫描一个结点计数器加一,直到扫描到表尾结束,返回计数器的最终值。代码如下。
int lincklength(linck* head)
{
linck* L = head;
int i = 0;
while (L->next != NULL)
{
L = L->next;//扫描下一个结点
i++;
}
return i;
}
单链表的插入
这个插入与前面我们学习的头插尾插不同,这是在链表的中间插入,这就会牵扯到插入位置前后的结点的,如果我们要在到第i个结点前插入时,具体操作为:
1.将要插入的数据赋给新结点的数据域
2. 将i-1个结点的指针域赋给新结点的指针域
3.将i-1个结点的指针域指向新结点
int Inslinck(linck*head,int i,char e)
{
linck* L = head;
int j = 0;
if (i <= 0)
return 0;
while (j < i - 1 && L != NULL)
{
L = L->next;
j++;
}
if (L == NULL)
return 0;
linck* s = (linck*)malloc(sizeof(linck));
s->date = e;
s->next = L->next;
L->next = s;
return 1;
}
单链表的删除
直接上代码,代码中附上详解。
int deletelinck(linck* head, int i)
{
linck* L=head, * s;
int j = 0;
if (i <= 0)//判断能否删除
return 0;
while (L->next != NULL && (j < i - 1))//找到第i-1个结点,让L指向第i-1个结点
{
L = L->next;
j++;
}
if (L == NULL)//判断能否删除
return 0;
//如果可以删除
s = L->next;//使s指向第i个结点
L = s->next;//让第i-1个结点指向第i+1个结点
free(s);//释放第i个结点
}
单链表的合并
现有两个链表LA(2,4,4,5),LB(1,2,3),将LA,LB合并成LC(1,2,2,3,4,4,5)。
首先设置一个空链表LC,然后将LA,LB中的元素按顺序比较,将较小的元素按尾插法插到链表LC中。代码如下
void mergelinck(linck*LA,linck*LB)
{
linck* LC;
linck* pa=LA->next, * pb=LB->next;
LC = LA;
linck* r = LC;
while (pa != NULL && pb != NULL)
{
if (pa->date <= pb->date)
{
r->next = pa;
pa = pa->next;
r = r->next;
}
else
{
r->next = pb;
pb = pb->next;
r = r->next;
}
}
if (pa)
{
r->next = pa;
r = r->next;
}
if (pb)
{
r->next = pb;
r = r->next;
}
free(LA);
free(LB);
}
循环链表
循环链表就像一条首尾相接的绳子,把单链表的尾结点的指针域指向单链表的头结点,这样就变成了一个循环链表
建立循环链表
void creatCLlinck(linck* CL)
{
linck* L=CL;
char c;
c = getchar();
while (c != '^')
{
linck* s = (linck*)malloc(sizeof(linck));
s->date = c;
L->next = s;
L = s;
c = getchar();
}
L->next = CL;
}
循环链表还有一些插入,删除,合并操作,学到这里已经可以自己稍加分析来操作了。
双向链表
我们要想在单向链表中找到目标结点只能从前往后遍历,指针只能通过后驱结点向后移动,但是双向链表增加了一个前驱指针,可以通过前驱指针找到前一个元素,这样就使得运算变得更加方便。
建立双向链表
定义双向链表的结构体:
typedef struct DoubleLinkedNode{
char data;
struct DoubleLinkedNode *previous;
struct DoubleLinkedNode *next;
}DLNode;
建立双向链表:
Node* creatList(Node* head, int length)
{
if (length == 1)
{
return(head = creatNode(head));
}
else
{
head = creatNode(head);
Node* list = head;
for (int i = 1; i < length; i++)
/*创建并初始化一个新结点*/
{
Node* body = (Node*)malloc(sizeof(Node));
body->pre = NULL;
body->next = NULL;
body->data = rand() % MAX;
/*直接前趋结点的next指针指向新结点*/
list->next - body;
/*新结点指向直接前趋结点*/
body->pre = list;
/*把body指针给1ist返回*/
list = list->next;
}
}
return head;
}