有头节点的单链表(C语言实现)
总纲
- 单链表头节点的定义
- 单链表的初始化
- 单链表元素的插入
- 单链表元素的删除
- 单链表元素的查找
- 单链表元素的索引
- 单链表元素的修改
- 单链表的打印
- 单链表的销毁
一. 单链表头节点的定义
单链表每个节点的数据可以是任意类型,所以可以用typedef把数据类型用一个变量存起来
一个节点由两部分组成:
- 数据域:存储该节点的数据
- 指针域:存储指向下一个节点的指针
typedef int type; // 将类型重命名
typedef struct Node
{ // 链表节点结构体
type data; // 数据域
struct Node* next; // 指针域
}ListNode; // 同样为了方便,将struct node重命名
typedef struct link_list
{
int size; // 存链表大小
ListNode* head; // 头节点,是应指向第一个元素的指针 // 链表整体结构体
}LinkedList;
其实这里也可以不创建链表整体的结构体,看个人喜好吧
二. 单链表的初始化
链表初始化后无元素,就只有一个头节点
void init(LinkedList* list)
{
list->head = NULL; // 头节点置空
list->size = 0; // 此时链表内无元素,所以size为0
}
三. 单链表元素的插入
这里提供三种插入方法:头插; 尾插; 任意位置插入
1. 头插
头插就是插到头节点之后,所有元素之前
void HeadInsert(LinkedList* list, type x) // x是要插入的元素
{
ListNode* tmp = (ListNode*)malloc(sizeof(ListNode)); // 创建一个节点
if (tmp != NULL) // 检查分配空间是否成功
{
list->size ++;
tmp->data = x;
tmp->next = list->head;
list->head = tmp;
}
/*
注意这里tmp->next要指向head原来指向的那个元素,head后可能已经有一个元素了
然后head要指向新插入的这个元素。用tmp的原因是不能改变头节点
*/
else
{
printf("HeadInsertError");
return;
}
}
2. 尾插
尾插就是插到所有元素之后啦
注意在插入这个元素之前,最后一个元素是指向NULL的
void TailInsert(LinkedList* list, type x)
{
ListNode* pre = (ListNode*)malloc(sizeof(ListNode)); // 先创建一个节点
if (pre != NULL) // 检查空间是否分配成功
{
pre->data = x; // x存进pre中
pre->next = NULL; // 该pre指向NULL
ListNode* tmp = list->head;
while(tmp->next != NULL)
{
tmp = tmp->next;
}
tmp->next = pre; // 这时tmp就是最后一个元素了,要指向pre
list->size ++;
}
else
{
printf("TailInsertError\n");
}
}
3. 任意位置插入
这个时候要判断插入位置合不合法了
因为涉及到头节点时要改变头节点,所以要分开讨论了
void insert(LinkedList* list, int index, type x)
{
if (index < 0 || index >= list->size)
{
printf("invalid index\n");
return;
}
ListNode* tmp = (ListNode*)malloc(sizeof(ListNode)); // 先创建一个节点保存这个数据
if (tmp != NULL)
{
if (index == 0) //如果是头插入
{
HeadInsert(list, x);
}
else if (index == list->size - 1) //如果是尾插入
{
TailInsert(list, x);
}
else
{
ListNode* start = list->head; // 定义一个左指针便于寻找位置
for (int i = 1; i < index; i++)
{
start = start->next; // for循环控制指针移动的次数
} // 这时 start在index前一个位置
tmp->next = start->next; // 这个元素(插入元素)要指向原来位置是index的元素,
start->next = tmp; // start接下来要指向这个元素
tmp->data = x; // 存数据
list->size ++; // 长度增加1
}
}
}
四. 单链表元素的删除
因为涉及到头节点的话要修改头节点,所以要分类讨论
注意要释放被删除元素的那个空间,也要判断位置是否合法
void Delete(LinkedList* list, int index)
{
if (index < 0 || index >= list->size) // 检查位置是否合法
{
printf("invalid index\n");
return;
}
if (index == 0) // 这个时候要修改头节点
{
ListNode* tmp = list->head; // 为释放空间做准备 注意这里head存的就是第一个(也就是要删的)的地址
list->head = list->head->next; // head 存的是原来第二个元素的地址了
free(tmp); // 释放原来第一个元素地址所指向的内存空间
tmp = NULL; // 置空
}
else
{
// 这里不需要修改头节点,要找到正确的位置
ListNode* start = list->head; // 定义一个指针便于寻找位置
for (int i = 0; i < index - 1; i++)
{
// for循环控制指针移动次数
start = start->next;
}// 这时指针已经来到了要被删除元素前一个元素的位置
ListNode* tmp_2 = start->next; // 这个是要被删除的元素
start->next = tmp_2->next; // start要指向被删除元素的后一个元素
free(tmp_2); // 释放被删除元素地址所指向的空间
tmp_2 = NULL; // 置空
}
list->size--; // 不要忘记长度要减一
}
五. 单链表元素的查找
返回符合条件的第一个元素的索引
int find(LinkedList* list, type x)
{
int ret = 0; // 返回的结果
ListNode* tmp = list->head; // 定义一个指针,用来避免修改头节点
while(tmp->data != x && tmp != NULL)
{
tmp = tmp->next;
ret++;
}
if (tmp == NULL)
{
ret = -1; // 找不到返回-1,说明没有
}
return ret;
}
六. 单链表元素的索引
根据索引值来找到对应的元素,记得判断索引是否合法
int value(LinkedList* list, int index)
{
if (index < 0 || index >= list->size)
{
return -1; // 判断索引是否合法
}
ListNode* tmp = list->head; // 同样,是为了避免修改头节点
while(index--)
{
tmp = tmp->next;
}
return tmp->data;
}
七. 单链表元素的修改
根据索引找到那个元素,然后修改
void set(LinkedList* list, int index, type x)
{
if (index < 0 || index >= list->size)
{
printf("invalid index");
return;
}
ListNode* tmp = list->head;
while(index--)
{
tmp = tmp->next;
}
tmp->data = x;
}
与单链表元素的索引大致相同
九. 单链表的打印
循环打印
void printlist(LinkedList* list)
{
ListNode* tmp = list->head;
while(tmp != NULL)
{
printf("%d ",tmp->data);
tmp = tmp->next;
}
}
十. 单链表的销毁
每一个节点指向的内存空间都要释放,指针要置空
void destroy(LinkedList* list)
{
while(list->head != NULL)
{
ListNode* tmp = list->head;
list->head = tmp->next;
free(tmp);
tmp = NULL;
list->size--;
}
}