单链表的介绍
1.什么是单链表
线性表的链式存储又称为单链表,它是指通过一组任意的存储单元来存储线性表中的数据元素。为了建立数据元素之间的线性关系,对每个链表节点,除存放自身的信息外,还需要存放一个指向其后继的指针。
2.单链表和顺序表的对比
顺序表可以随机访问表中的任意元素,但插入和删除需要移动大量的元素。链式存储线性表时,不需要使用地址连续的存储单元,它通过链建立起元素之间的关系,所以插入和删除不需要移动元素,只需要修改指针,但也会失去顺序表可随机存储的优点。利用单链表可以解决顺序表大量连续储存单元的缺点,单链表存在指针域,有浪费空间的缺点。在查找某个特定的结点时,需要从表头遍历,依次查找。
单链表的实现
1.单链表的定义
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SLTNode;
单链表的data是用来保存数据的,我们对data重命名这样当我们需要保存不同类型的数据时候便于修改,同时单链表也需要定义一个指针去存储下一个结点的地址。
2.单链表的尾插
void SLTpushBack(SLTNode** phead, SLTDateType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//结辟节点
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = NULL;//结点赋值
if (*phead == NULL)
{
*phead = newnode;
}
else
{
SLTNode* tail = *phead;
while (tail->next != NULL)
{ //结点插入
tail = tail->next;
}
tail->next = newnode;
}
}
时间复杂度O(N)。
单链表的尾插首先开辟一个新的节点,然后让链表最后一个元素的next指向新的结点,新结点的next指向下一个节点也就是NULL,这样让链表进行链接起来。单链表只能找到下一个节点的位置,想要找到前一个节点的位置必须从头开始遍历,因此这样效率很低。
3.单链表的头插
void SLTpushFront(SLTNode* *head, SLTDateType x)
{
assert(*head);
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = NULL;
newnode->next = *head;
*head = newnode;
}
这里的传入的是指针的地址,要想修改它必须要用二级指针。想改变int需要用int*,要想改变int*需要int**,也就是二级指针。我们定义的是一个结构体指针变量,传入的是结构体指针变量的地址,要想改变指针变量必须用二级指针。结构体指针的变量的地址一定要非空,而链表可能非空所以我们必须对代码进行断言操作。
单链表每个结点的插入时间复杂度为O(1),设单链表的长度为n,总时间复杂度为O(N)。
4.单链表的头删
void SLTpopFront(SLTNode** head)
{
assert(*head);
SLTNode* first = *head;
*head = first->next;
free(first);
first = NULL;
}
首先定义一个first指针,然后找到phead->next的地址,释放掉phead 的空间,让phead重新 指向phead->next。该算法的时间复杂度为O(1)。
5.单链表尾删
void SLTpopBack(SLTNode** head)
{
assert(*head);
if ((*head)->next == NULL)
{
free(*head);
*head = NULL;
}
else
{
SLTNode* prev = NULL;
SLTNode* tail = *head;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
prev->next = NULL;
}
}
尾部删除要改变前一个节点的next指针,需要遍历节点,时间复杂度为O(N)。
6.删除pos后的数据
void SLTease(SLTNode** head, SLTNode* pos)
{
assert(head);
assert(pos);
SLTNode* prev = *head;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
要删除某个结点通常做法是先从链表的头结点找到前驱节点,然后执行删除操作,实际就是将其后继节点的值给本身,然后删除后继点。
7.在pos位置后插入数据
void SLTinsert(SLTNode** head, SLTNode* pos, SLTDateType x)
{
assert(pos);
assert(head);
SLTNode* prev = *head;
while (prev->next != pos)
{
prev = prev->next;
}
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = NULL;
prev->next = newnode;
newnode->next = pos;
}
单链表的打印
void SLTprint(SLTNode* head)
{
SLTNode* tail = head;
while (tail)
{
printf("%d->", tail->data);
tail = tail->next;
}
printf("NULL\n");
}
单链表的数据查找
void SLTFind(SLTNode* head, SLTDateType x)
{
SLTNode* cur = head;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
总代码
#include"slist.h"
void Test()
{
SLTNode* plist=NULL;
SLTpushBack(&plist, 1);
SLTpushBack(&plist, 2);
SLTpushBack(&plist, 3);
SLTpushBack(&plist, 4);
SLTprint(plist);
SLTpushFront(&plist, 9);
SLTprint(plist);
SLTpopFront(&plist);
SLTprint(plist);
SLTpopBack(&plist);
SLTprint(plist);
SLTinsert(&plist, 2, 3);
SLTprint(plist);
SLTease(&plist, 2);
SLTprint(plist);
SLTFind(plist, 3);
}
int main()
{
Test();
return 0;
}
#include"slist.h"
void SLTpushBack(SLTNode** phead, SLTDateType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = NULL;
if (*phead == NULL)
{
*phead = newnode;
}
else
{
SLTNode* tail = *phead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
void SLTprint(SLTNode* head)
{
SLTNode* tail = head;
while (tail)
{
printf("%d->", tail->data);
tail = tail->next;
}
printf("NULL\n");
}
void SLTpushFront(SLTNode* *head, SLTDateType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = NULL;
newnode->next = *head;
*head = newnode;
}
void SLTpopFront(SLTNode** head)
{
assert(*head);
SLTNode* first = *head;
*head = first->next;
free(first);
first = NULL;
}
void SLTpopBack(SLTNode** head)
{
assert(*head);
if ((*head)->next == NULL)
{
free(*head);
*head = NULL;
}
else
{
SLTNode* prev = NULL;
SLTNode* tail = *head;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
prev->next = NULL;
}
}
void SLTinsert(SLTNode** head, SLTNode* pos, SLTDateType x)
{
assert(pos);
assert(head);
SLTNode* prev = *head;
while (prev->next != pos)
{
prev = prev->next;
}
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = NULL;
prev->next = newnode;
newnode->next = pos;
}
void SLTease(SLTNode** head, SLTNode* pos)
{
assert(head);
assert(pos);
SLTNode* prev = *head;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
void SLTFind(SLTNode* head, SLTDateType x)
{
SLTNode* cur = head;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
#include<stdlib.h>
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SLTNode;
void SLTpushBack(SLTNode** phead, SLTDateType x);
void SLTprint(SLTNode* head);
void SLTpushFront(SLTNode** head, SLTDateType x);
void SLTpopFront(SLTNode** head);
void SLTpopBack(SLTNode** head);
void SLTinsert(SLTNode** head, SLTNode* pos, SLTDateType);
void SLTease(SLTNode** head, SLTNode* pos);
void SLTFind(SLTNode* head, SLTDateType x);