今天的主题是链表,其实链表说难也难,说简单也简单,完全取决于我们是否完全把它全部理解透。链表其实有很多种,带头的、不带头的,单向的、双向的,循环的、不循环的,组合起来应该是有很多种的(8种)但是我这里只给大家介绍两种,一种是不带头单向不循环链表,也就是大多数书上说的单链表。ok废话少说,直接进入主题:
1.不带头单向不循环链表,我这里就介绍增删查改……等等这几个操作:
首先我们需要有一个单链表进行操作:
typedef int SLNDataType;
//Single List
typedef struct SListNode
{
SLNDataType val;
struct SListNode* next;
}SLNode;
这里需要因为链表我们不能保证只能操作整形数据,所以我们直接typedef一下,将来有可以直接修改数据类型,我们知道,链表的每一个节点(结点)都存储了下一个节点的地址,所以我们需要一个结构体指针进行操作:
1.1初始化链表,然后开始尾插链表(包含尾删操作):
void SLTPrint(SLNode* phead)
{
SLNode* cur = phead;
while (cur)
{
printf("%d->", cur->val);
cur = cur->next;
}
printf("NULL");
printf("\n");
}
SLNode* Create(SLNDataType x)
{
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->val = x;
newnode->next = NULL;
return newnode;
}
void SLTPushBack(SLNode** pphead, SLNDataType x)
{
assert(pphead);
SLNode* newnode = Create(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//这里是因为我要找到尾节点(结点)才能插入
SLNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
void SLTPopBack(SLNode** pphead)
{
assert(pphead);
assert(*pphead);
if ((*pphead)->next == NULL)//一个节点的尾删
{
free(*pphead);
*pphead = NULL;
}
else
{
//SLNode* tail = *pphead;
//SLNode* prev = NULL;
//while (tail->next != NULL)
//{
// prev = tail;
// tail = tail->next;
//}
//free(tail);
//tail = NULL;
//prev->next = NULL;
SLNode* tail = *pphead;
while (tail->next->next != NULL)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
第一个函数是打印函数,相信大家很好理解,第二个是创作出新节点,方便我们进行尾插操作,
大概就是这样子的,具体的需要大家去好好理解,并且要去查阅资料,方便大家理解尾插操作。
运行测试大概是这样子的:
尾删:
这里大家去测试的代码我就不放出来了,因为这就是一个函数的使用而已。我们继续
1.2头插、头删操作:
void SLTPushFront(SLNode** pphead, SLNDataType x)
{
assert(pphead);
SLNode* newnode = Create(x);
newnode->next = *pphead;
*pphead = newnode;
}
void SLTPopFront(SLNode** pphead)
{
assert(pphead);
assert(*pphead);
//多个节点+一个节点都可以处理
//SLNode* next = (*pphead)->next;
//free(*pphead);
//*pphead = next;
SLNode* tem = *pphead;
*pphead = (*pphead)->next;
free(tem);
tem = NULL;
}
这里其实也是一样的,大家要注意我们为什么要使用二级指针进行操作,因为我们知道,在进行相关的操作的时候我们要进行判断,判断pphead是否是空,如果是空的情况下我们要去改变pphead,但是pphead是一个结构体指针,要改变结构体指针,那么我们只能用二级指针操作了。
头插、头删:
1.3指定位置的操作(包括查找、删除):
SLNode* SLTFind(SLNode** pphead, SLNDataType x)
{
assert(*pphead);
SLNode* cur = *pphead;
while (cur)
{
if (cur->val == x)
return cur;
else
cur = cur->next;
}
return NULL;
}
void SLTInsert(SLNode** pphead,SLNode* pos,SLNDataType x)
{
assert(pphead);
assert(*pphead);
assert(pos);
//assert((!pos && !(*pphead)) || (*pphead && pos));
//头插(没有前一个),尾插,中间节点都是要插入,找到前一个
if (*pphead == pos)
{
SLTPushFront(pphead, x);
}
else
{
SLNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SLNode* newnode = Create(x);
prev->next = newnode;
newnode->next = pos;
}
}
void SLTErase(SLNode** pphead, SLNode* pos)
{
assert(pphead);
assert(*pphead);
assert(pos);
if (*pphead == pos)
{
SLTPopFront(pphead);
}
else
{
SLNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
void SLTDestroy(SLNode** pphead)
{
assert(*pphead);
while ((*pphead)->next != NULL)
{
SLTPopFront(pphead);
}
free(*pphead);
*pphead = NULL;
//SLNode* cur = *pphead;
//while (cur)
//{
// SLNode* temp = cur->next;
// free(cur);
// cur = temp;
//}
}
请注意:我们这里的指定位置的意思是在指定位置的前面插入
这里我直接把销毁的代码也给出来了,大家好好参考一下
这里的测试代码我应该不需要给出吧(多动手才能成功),大家自己去尝试一下,我都写出函数了,我相信函数的调用大家应该是没问题的!
1.4指定位置的插入、删除(在指定位置的后面插入、删除):
void SLTInsertAfter(SLNode* pos, SLNDataType x)
{
assert(pos);
SLNode* newnode = Create(x);
newnode->next = pos->next;
pos->next = newnode;
}
void SLTEraseAfter(SLNode* pos)
{
assert(pos);
assert(pos->next);
SLNode* tem = pos->next;
pos->next = pos->next->next;
free(tem);
tem = NULL;
}
这里还是一样提醒大家去多多尝试写代码,相信大家也会成功的!!!!
ps:这里为什么给大家讲这种不带头单项不循环链表吗?因为OJ题大部分都是这个类型的链表,所以我这里介绍的是这种。还有一种双向带头循环链表,我下次讲吧(敬请各位大佬指正,谢谢!)