文章目录
1. 单向、不带头、不循环链表介绍
无头单向非循环链表:结构简单,但是实现接口时比较繁琐,一般不会用来单独存储数据,大部分作为其他结构的子结构应用。
2. 单链表结构体、常用函数接口
// 单向 不带头 不循环
typedef int SLTDataType;
typedef struct SListNode // 单链表的一个结点
{
SLTDataType _data;
struct SListNode* _next;
}SListNode;
typedef struct SList // 单链表
{
SListNode* _head; // 头指针
}SList;
定义了两个结构体,第一个结构体是单链表的结点(数据域和指针域),第二个结构体定义了指向单链表头指针。
单链表的函数接口:
void SListInit(SList* plt); // 初始化
void SListPushBack(SList* plt, SLTDataType x);// 尾插
void SListPushFront(SList* plt, SLTDataType x);// 头插
void SListPopBack(SList* plt);// 尾删
void SListPopFront(SList* plt);// 头删
SListNode* SlistFind(SList* plt, SLTDataType x);// 寻找某个指定结点
void SListInsertAfter(SListNode* pos, SLTDataType x);// 指定位置后面添加
void SListEraseAfter(SListNode* pos);// 指定位置后面删除
void SListRemove(SList* plt, SLTDataType x);// 已知值的第一个结点删除
void SListRemoveAll(SList* plt, SLTDataType x);// 已知值的所有结点删除
void SListPrint(SList* plt);// 打印
void SListDestory(SList* plt);// 清空
3. 单链表接口的实现
3.1 初始化
初始化时,链表中没有结点,所有把头指针置空。
void SListInit(SList* plt) // 初始化
{
assert(plt);
plt->_head = NULL;
}
3.2 尾插
尾插一个新结点,先malloc一个新结点,再找到单链表最后一个结点,让最后一个结点的指针域指向新结点,最后新结点的指针置空。
void SListPushBack(SList* plt, SLTDataType x) // 尾插
{
assert(plt);
SListNode* newnode = (SListNode*)malloc(sizeof(SListNode)); // 申请一个新结点
newnode->_data = x;
newnode->_next = NULL;
// 1.空
// 2.非空
if (plt->_head == NULL)
{
plt->_head = newnode;
}
else
{
SListNode* cur = plt->_head;
while (cur->_next != NULL)
{
cur = cur->_next;
} // 找到尾结点
cur->_next = newnode;
}
}
3.3 头插
头插一个新结点,先malloc一个新结点,将新结点的指针指向第二个结点,然后将头指针指向新的头结点。(这里不能把这两个步骤颠倒,如果先将头指针指向新结点的话,会丢失指向第二个结点的指针,当然你也可以提前定义一个新指针,保存这个指向第二个结点的指针,这就没必要了)。
void SListPushFront(SList* plt, SLTDataType x)// 头插
{
assert(plt);
SListnewnode = (SListNode*)malloc(sizeof(SListNode)); // 申请新结点
newnode->_data = x;
newnode->_next = NULL;
newnode->_next = plt->_head;
plt->_head = newnode;
}
3.4 尾删
定义两个指向结点的指针,一前一后找到最后一个元素,n1指向倒数第二个结点,n2指向最后一个结点,释放掉n2,记得要把n2置空,防止野指针。再把n1的指针域置空。
void SListPopBack(SList* plt) // 尾删
{
assert(plt);
if (plt->_head == NULL)
{
printf("链表为空!\n");
return;
}
else
{
SListNode* n1 = plt->_head;
SListNode* n2 = plt->_head;
while (n2->_next != NULL)
{
n1 = n2;
n2 = n2->_next;
}// 找到了最后一个结点
free(n2);
n2 = NULL; // 置空
n1->_next = NULL;
}
}
3.5 头删
定义一个指向头结点的指针cur,再将指向第二个结点的指针(cur->_next)给头指针,释放掉cur指向的头结点,最后将cur置空。
void SListPopFront(SList* plt) // 头删
{
assert(plt);
if (plt->_head == NULL)
{
printf("链表为空!\n");
return;
}
else
{
SListNode* cur = plt->_head;
plt->_head = cur->_next;
free(cur);
cur = NULL;// 防止野指针
}
}
3.6 寻找指定结点
定义一个指针cur,遍历单链表,依次比较,如果cur的值等于给定值,返回指针cur。
SListNode* SlistFind(SList* plt, SLDataType x) // 寻找某个指定结点
{
assert(plt);
if (plt->_head == NULL)
{
printf("链表为空!\n");
return;
}
else
{
SListNode* cur = plt->_head;
while (cur != NULL)
{
if (cur->_data == x)
{
return cur; // 找到并返回
}
else
{
cur = cur->_next;
}
}
return NULL;
}
}
3.7 指定位置后添加
已知pos指针,直接在其后面添加,让新结点指针指向pos的下一个结点,pos结点的指针指向新结点(这两个步骤不能颠倒)。
void SListInsertAfter(SListNode* pos, SLTDataType x)// 指定位置后面添加
{
assert(pos);
SListNode* newnode = (SListNode*)malloc(sizeof(SListNode)); // 申请新结点
newnode->_data = x;
newnode->_next = NULL;
newnode->_next = pos->_next;
pos->_next = newnode;
}
3.8 指定位置后删除
定义一个apos指针指向pos后面的结点,让pos的指针指向apos的下一个结点,此时可以安全释放掉apos指向的结点,最后将apos置空。
void SListEraseAfter(SListNode* pos)// 指定位置后面删除
{
assert(pos);
if (pos->_next == NULL)
{
return;
}
else
{
SListNode* apos = pos->_next;
pos->_next = apos->_next;
free(apos);
apos = NULL;
//下面这种方法是错误的,因为pos->_next->_next可能指向非法地址。
//pos->_next = pos->_next->_next;
//free(pos->_next);
}
}
3.9 删除第一个指定值结点
定义两个指针fcur和cur,一前一后,cur找指定值,找到后让fcur的_next指针指向cur的下一个结点,释放掉cur,cur置空。(注意各种边界条件!)
void SListRemove(SList* plt, SLTDataType x)// 已知值的第一个结点删除
{
assert(plt);
if (plt->_head == NULL)
{
printf("链表为空!\n");
return;
}
else
{
SListNode* fcur = NULL;
SListNode* cur = plt->_head;
while (cur != NULL)
{
if (cur->_data != x)
{
fcur = cur;
cur = cur->_next;
}
else
{
if (fcur == NULL)
plt->_head = cur->_next;
else
fcur->_next = cur->_next;
free(cur);
cur = NULL;
return;
}
}
}
}
3.10 删除指定值所有结点
定义两个指针prev和cur,一前一后,用循环控制多次删除,删除的方式同上,往后遍历依次比较(注意各种边界条件!)。
void SListRemoveAll(SList* plt, SLTDataType x)// 已知值的所有结点删除
{
assert(plt);
if (plt->_head == NULL)
{
printf("链表为空!\n");
return;
}
else
{
SListNode* prev = NULL;
SListNode* cur = plt->_head;
while (cur != NULL)
{
if (cur->_data == x)
{
if (prev == NULL)
{
SListNode* next = cur->_next;
plt->_head = next;
free(cur);
cur = next;
}
else // prev!=NULL
{
SListNode* next = cur->_next;
prev->_next = next;
free(cur);
cur = next;
}
}
else
{
prev = cur;
cur = cur->_next;
}
}
}
}
3.11 清空
定义cur指针,逐个遍历,逐个结点释放,最后释放置空。
void SListDestory(SList* plt) // 清空
{
assert(plt);
if (plt->_head == NULL)
{
printf("链表为空!\n");
return;
}
else
{
SListNode* cur = plt->_head;
while (cur != NULL)
{
SListNode* next = cur->_next;
plt->_head = next;
free(cur);
cur = next;// 防止野指针
}
}
}
3.12 打印
定义cur指针,逐个遍历,依次打印。
void SListPrint(SList* plt) // 打印
{
assert(plt);
SListNode* cur = plt->_head;
while (cur != NULL)
{
printf("%d->", cur->_data);
cur = cur->_next;
}
printf("NULL\n");
}