五十、 单链表
50.1 链表的概念
- 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
- 链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。
- 每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
50.2 链表与顺序表的区别
- 顺序表:
- 可以类似于数组,通过下标进行访问
- 结点数量在创建时就固定了
- 增删时会按需求移动结点,比较麻烦
- 在内存上是连续存储的
- 链表:
- 只能通过 next 指针域 进行顺序访问,不可以通过下标进行跳跃访问
- 结点数量可以动态申请和释放
- 增删时通过修改指针域的指向,不会移动结点
- 在内存上不是连续存储
50.3 链表的组成
- 数据域: 由一项或多项数据构成,数据类型不定
- 指针域: 该结构体类型的指针,指向结点首地址(看需求指向上一个、下一个、或者其他位置)
50.4 链表的功能实现
- 任意位置插入
- 任意位置删除
50.4.1 链表的结构体
// 重命名数据域的数据类型名
typedef int data;
// 数据域是一个联合体,指针域指向下一结点
typedef struct mylinkList
{
union
{
data num; // 表示数据内容
int len; // len表示当前有多少结点(头结点除外)
};
// 指向下一结点的指针
struct mylinkList *next;
} linkNode; // 结构体类型名重命名为linkNode
50.4.2 我实现的功能(接口)
// 创建单链表
linkNode *init();
// 单链表的判空
int is_empty(linkNode *L);
// 单链表的尾插
int tail_insert(linkNode *L, data number);
// 单链表的头插
int head_insert(linkNode *L, data number);
// 指定下标插入
int inset_by_index(linkNode *l, int index, data value);
// 指定下标删除
int del_by_index(linkNode *l, int index);
// 头删
int head_del(linkNode *l);
// 尾删
int tail_del(linkNode *l);
// 按值删除,返回删除的下标
int del_by_value(linkNode *l, data value);
// 单链表的按值查找,返回下标
int find_by_value(linkNode *l, data value);
// 单链表的按值修改,返回下标
int change_by_value(linkNode *l, data old_value, data new_value);
// 单链表的按位置查找,返回对应的值
data find_by_index(linkNode *l, int index);
// 单链表的选择排序
int select_sort(linkNode *l);
// 单链表的冒泡排序
int bubble_sort(linkNode *l);
// 输出单链表
int output(linkNode *l);
// 单链表去重
int remove_duplicates(linkNode *l);
// 释放单链表
int free_linkNode(linkNode **l);
// 单链表的翻转,循环头插
int flip_linkNode(linkNode *l);
50.4.3 功能实现代码
创建单链表
// 创建单链表
linkNode *init();
// 申请一个结构体大小的堆区空间,用p指向
linkNode *p = (linkNode *)malloc(sizeof(linkNode));
// 申请失败时返回NULL
if (NULL == p)
{
printf("创建顺序表失败\n");
return NULL;
}
// 初始结点数为0(头结点除外)
p->len = 0;
// 初始结点的指针域指向NULL
p->next = NULL;
// 返回申请的堆区空间的首地址
return p;
单链表的判空
// 单链表的判空
int is_empty(linkNode *L);
// 只有一个头结点,或者结点数为0(头结点除外),此时链表为空,返回1
if (NULL == l->next && l->len == 0)
{
return 1;
}
return 0; // 链表不是空,返回0
单链表的尾插
// 单链表的尾插
int tail_insert(linkNode *L, data number);
// 防止头结点指向改变,故新建一个指针指向头结点
linkNode *p = l;
// 循环向后指,直到p指向最后一个结点
while (NULL != p->next)
{
p = p->next;
}
// 新申请一个结点的空间,用ll指向
linkNode *ll = (linkNode *)malloc(sizeof(linkNode));
// 新结点的值为传进来的值
ll->num = value;
// 新结点的next指针域指向NULL
ll->next = NULL;
// 新结点插到最后
p->next = ll;
// 结点计数变量+1
l->len++;
单链表的头插
// 单链表的头插
int head_insert(linkNode *L, data number);
// 防止头结点指向改变,故新建一个指针指向头结点
linkNode *p = l;
// 新申请一个结点的空间,用ll指向
linkNode *ll = (linkNode *)malloc(sizeof(linkNode));
// 新结点的值为传进来的值
ll->num = value;
// 新结点的next指针域指向头结点的原下一结点,即原第一结点
ll->next = p->next;
// 让头结点指向这个新结点
p->next = ll;
// 结点计数变量+1
l->len++;
指定下标插入
// 指定下标插入
int inset_by_index(linkNode *l, int index, data value);
// 防止头结点指向改变,故新建一个指针指向头结点
linkNode *p = l;
// 记录当前下标
int i = 1;
while (1)
{
// 如果当前下标与传进来的下标相等,进行头插
// 此时指针p已经循环指向了此下标对应的结点
if (i == index)
{
//head_insert(p, value); // 不能直接调用头插法,因为传进来的头结点不是真正头结点
linkNode *ll = (linkNode *)malloc(sizeof(linkNode));
ll->num = value;
ll->next = p->next;
p->next = ll;
// 插入后退出循环
break;
}
// 如果找到了最后还没找到,说明位置参数不对
if (NULL == p->next)
{
printf("传入位置参数不合法\n");
// 位置参数不对,return -1 表示函数异常结束
return -1;
}
// 这一圈没找到则当前下标+1向后找
++i;
// p继续向后指,向后接着找
p = p->next;
}
// 找到并插入后,结点数+1
l->len++;
头删
// 头删
int head_del(linkNode *l);
// 防止头结点指向改变,故新建一个指针指向头结点
linkNode *p = l;
// 保存待删除的结点,第一结点
linkNode *q = l->next;
// 将头结点的next指针域指向下下一结点
p->next = q->next;
// 释放第一结点的空间
free(q);
// 结点计数变量-1
l->len--;
尾删
// 尾删
int tail_del(linkNode *l);
// 防止头结点指向改变,故新建一个指针指向头结点
linkNode *p = l;
// 循环找到最后一个结点
while (1)
{
// 如果下一结点的next指针域指向NULL
// 说明当前结点是倒数第二个结点
if (NULL == p->next->next)
{
// q指向最后这个结点
linkNode *q = p->next;
// 倒数第二个结点的next指针域指向NULL
p->next = NULL;
// 释放最后这个结点的空间
free(q);
// 退出循环
break;
}
// 当前结点不是倒数第二个结点时继续向下找
p = p->next;
}
// 删除后,结点计数变量-1
l->len--;
指定下标删除
// 指定下标删除
int del_by_index(linkNode *l, int index);
// 防止头结点指向改变,故新建一个指针指向头结点
linkNode *p = l;
// 记录当前下标
int i = 1;
while (1)
{
// 如果当前下标与传进来的下标相等,进行删除
// 此时指针p的next指针域已经循环指向了要找的下标对应的结点
// 即p指向要删除的结点的前一结点
if (i == index)
{
// q指向这个要删除的结点
linkNode *q = p->next;
// 让要删除的结点前一结点和后面一个结点连接
// 越过要删除的这个结点
p->next = q->next;
// 释放要删除的这一结点的空间
free(q);
q = NULL;
// 删除后退出循环
break;
}
// 循环到了最后还没找到这个下标对应的结点
if (NULL == p->next)
{
// 提示位置参数不对,并返回-1表示函数异常结束
printf("传入位置参数不合法\n");
return -1;
}
// 这一圈没找到则当前下标+1向后找
++i;
// p继续向后指,向后接着找
p = p->next;
}
// 删除后,结点计数变量-1
l->len--;
按值删除,返回删除的下标
// 按值删除,返回删除的下标
int del_by_value(linkNode *l, data value);
// 防止头结点指向改变,故新建一个指针指向头结点
linkNode *p = l;
// 记录当前下标
int i = 1;
// 循环向后找
while (1)
{
// 如果找到了最后还没找到就退出循环
if (NULL == p->next)
{
break;
}
// 如果下一结点的值与传进来的值相同,则进行删除
if (value == p->next->num)
{
// 指针q指向下一结点,即要删除的结点
linkNode *q = p->next;
// 当前结点的next指针域指向要删除的结点的下一个结点
p->next = q->next;
// 释放要删除的结点的空间
free(q);
l->len--;
// 返回当前所在下标
return i;
}
// 这一圈没找到则当前下标+1向后找
++i;
// p继续向后指,向后接着找
p = p->next;
}
// 循环没找到,输出找不到这个值
printf("找不到这个值\n");
// 返回-1表示函数异常结束
return -1;
单链表的按值查找,返回下标
// 单链表的按值查找,返回下标
int find_by_value(linkNode *l, data value);
// 防止头结点指向改变,故新建一个指针指向头结点的下一结点
linkNode *p = l->next;
// 记录当前位置
int i = 1;
// 循环向后找
while (1)
{
// 如果下一结点的值与传进来的值相同,则返回当前位置
if (value == p->num)
{
return i;
}
// 如果找到了最后还没找到就退出循环
if (NULL == p->next)
{
break;
}
// 这一圈没找到则当前下标+1向后找
++i;
// p继续向后指,向后接着找
p = p->next;
}
// 循环没找到,输出找不到这个值
printf("链表中没有这个值\n");
// 返回-1表示函数异常结束
return -1;
单链表的按值修改,返回下标
// 单链表的按值修改,返回下标
int change_by_value(linkNode *l, data old_value, data new_value);
// 防止头结点指向改变,故新建一个指针指向头结点的下一结点
linkNode *p = l->next;
// 记录当前位置
int i = 1;
// 循环向后找
while (1)
{
// 如果下一结点的值与传进来的旧值相同,则用新值进行修改并返回当前位置
if (old_value == p->num)
{
p->num = new_value;
return i;
}
// 如果找到了最后还没找到就退出循环
if (NULL == p->next)
{
break;
}
// 这一圈没找到则当前下标+1向后找
++i;
// p继续向后指,向后接着找
p = p->next;
}
// 循环没找到,输出找不到这个值
printf("链表中没有这个值\n");
// 返回-1表示函数异常结束
return -1;
单链表的按位置查找,返回对应的值
// 单链表的按位置查找,返回对应的值
data find_by_index(linkNode *l, int index);
// 防止头结点指向改变,故新建一个指针指向头结点的下一结点
linkNode *p = l->next;
// 记录当前位置
int i = 1;
// 循环向后找
while (1)
{
// 如果当前位置与传进来的下标相同,则返回这个位置的值
if (i == index)
{
return p->num;
}
// 如果找到了最后还没找到则提示位置参数不对
if (NULL == p->next)
{
printf("传入位置参数不合法\n");
// 返回-1表示函数异常结束
return -1;
}
// 这一圈没找到则当前下标+1向后找
++i;
// p继续向后指,向后接着找
p = p->next;
}
单链表的选择排序,降序
// 单链表的选择排序,降序
int select_sort(linkNode *l);
// 防止头结点指向改变,故新建一个指针指向头结点的下一结点
// 结点p后面表示当前结点,结点指针q后面表示被比较的的结点
linkNode *p = l->next, *q;
// 外层循环,控制循环次数
while (1)
{
// 后面没有结点了,结束外层循环
if (NULL == p->next)
{
break;
}
// 指针q指向p的下一结点
// 表示被比较的结点是从当前结点的下一结点开始,会到最后一个结点
q = p->next;
// 假设最大值的结点是当前结点
linkNode *max = p;
// 内层循环,表示比较次数
while (1)
{
// 如果被比较的结点比当前认定的最大结点的值还大
if (q->num > max->num)
{
// 那么让最大结点指针指向这个结点
max = q;
}
// 如果被比较的结点到了最后一个,则退出内层循环
if (NULL == q->next)
{
break;
}
// 被比较的结点逐个往后挪
q = q->next;
}
// 当前结点与最大结点指针指向的结点进行值交换
data t = max->num;
max->num = p->num;
p->num = t;
// 当前结点逐个往后挪
p = p->next;
}
单链表的冒泡排序,升序
// 单链表的冒泡排序,升序
int bubble_sort(linkNode *l);
// 防止头结点指向改变,故新建一个指针指向头结点的下一结点
// 结点指针p控制外层循环次数,结点指针end控制内层比较次数
// 结点指针q用于比较
linkNode *p = l->next, *q, *end = NULL;
// 外层循环,表示循环次数
while (1)
{
if (NULL == p->next) // 后面没有结点了,直接结束
{
break;
}
// 每次都从第一个数据结点开始(头结点后的第一结点)
q = l->next;
// 内层循环,表示比比较次数
while (1)
{
// 如果 当前值 大于 后面的值,则交换
if (q->num > q->next->num)
{
data t = q->num;
q->num = q->next->num;
q->next->num = t;
}
// 到了尚未比较的结点中倒数第二个结点,前面已经比较过了,该退出进行下一轮的比较了
if (end == q->next->next)
{
// 更新结束条件
end = q->next;
break;
}
// 内层循环递进,用于比较的结点向后逐步挪移
q = q->next;
}
// 外层循环递进,p逐渐向后指向,直到最后一个结点后进入下次循环时结束
p = p->next;
}
输出单链表
// 输出单链表
int output(linkNode *l);
// 防止头结点指向改变,故新建一个指针指向头结点
// 用于指向待输出的结点
linkNode *p = l;
// 记录当前到第几个结点
int i = 0;
while (1)
{
// 是头结点时
if (0 == i)
{
printf("除头结点外,其余结点个数为:%d\n", p->len);
}
else // 非头结点时
{
printf("第%d位数据为:%d\n", i, p->num);
}
// 位置变量+1
++i;
// 如果到了最后一个结点,那么就退出(因为是先输出的,所以已经输出国了)
if (NULL == p->next)
break;
// 待输出的结点向后逐位移动
p = p->next;
}
单链表去重
// 单链表去重
int remove_duplicates(linkNode *l);
// 防止头结点指向改变,故新建一个指针指向头结点
// 用于记录当前结点
linkNode *p = l;
// 外层循环,控制循环次数
while (1)
{
// 用于记录被比较结点的前一结点
linkNode *q = p;
// 内层循环,用于控制比较次数
while (1)
{
// 如果被比较的结点到了最后一位,则退出
if (NULL == q->next)
{
break;
}
// 如果当前结点的值与被比较的结点的值相同,则删除这个被比较的结点
if (q->next->num == p->num)
{
// 用于指向被比较的结点
linkNode *t = q->next;
// 被比较的前一结点的next指针域指向被比较的结点的后一结点
q->next = t->next;
// 释放这个被比较的结点
free(t);
t = NULL;
// 结点计数变量-1
l->len--;
}
// 让被比较的结点向后挪动
q = q->next;
}
// 让当前结点向后挪动
p = p->next;
// 如果当前结点挪到了最后了,则退出
if (NULL == p)
{
break;
}
}
释放单链表
// 释放单链表
int free_linkNode(linkNode **l);
// 用于接收传来的头结点指针
// 传来的是头结点指针的地址,所以需要解引用获得其指向的地址
linkNode *p = *l;
// 用于存放准备释放的结点的下一结点,免得找不到了
linkNode *q = p->next;
// 进入循环,准备逐个释放
while (1)
{
// 释放当前结点
free(p);
// 让下一结点变成当前结点
p = q;
// 如果当前结点到了最后,指针已经指向NULL,表示空间都释放了
// 所以需要将头结点指针的指向更改,所以需要解引用指向NULL,然后退出
if (NULL == p)
{
*l = NULL;
return 0;
}
// 待释放结点的下一结点向后挪
// 因为上面已经把原来待释放结点的下一结点变成了当前准备释放的结点了
q = q->next;
}
单链表的翻转,循环头插
// 单链表的翻转,循环头插
int flip_linkNode(linkNode *l);
// 让除了第一数据结点外的其他结点依次进行头插,即可得到翻转后的链表
// 如果直接调用头插法,会重新生成结点,导致空间浪费
// 这是个面试题
linkNode *p = l->next->next; // 记录当前需要头插的结点
// 将原本的第一数据结点的next指针域指向NULL
// 因为最后这个结点会变成尾结点
l->next->next = NULL;
linkNode *q = p->next; // 记录下次需要头插的结点
// 循环头插
while (1)
{
// 将当前需要插入的结点的next指针域指向现在的第一数据结点
p->next = l->next;
// 将头结点的next指针域指向当前需要插入的结点
// 这样当前需要插入的结点就完成了头插,变成了新的第一数据结点
l->next = p;
// 让下次需要头插的结点变成当前需要插入的结点
p = q;
// 如果下次需要头插的结点没有指向原尾结点的next指针域
if (NULL != q)
{
q = q->next;
}
// 如果当前下次需要插入的结点已经指向原尾结点的next指针域
// 即表示已经到了原尾结点的后一个结点了,那么就退出循环
// 这样可以将原尾结点也进行头插,不然会漏掉原尾结点
else
break;
}
50.5 单向链表完整代码(接口,功能实现,测试用主函数,Makefile)
50.5.1 接口(head.h)
#ifndef __HEAD_H__
#define __HEAD_H__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 重命名数据域的数据类型名
typedef int data;
// 数据域是一个联合体,指针域指向下一结点
typedef struct mylinkList
{
union
{
data num; // 表示数据内容
int len; // len表示当前有多少结点(头结点除外)
};
// 指向下一结点的指针
struct mylinkList *next;
} linkNode; // 结构体类型名重命名为linkNode
// 创建单链表
linkNode *init();
// 单链表的判空
int is_empty(linkNode *L);
// 单链表的尾插
int tail_insert(linkNode *L, data number);
// 单链表的头插
int head_insert(linkNode *L, data number);
// 指定下标插入
int inset_by_index(linkNode *l, int index, data value);
// 指定下标删除
int del_by_index(linkNode *l, int index);
// 头删
int head_del(linkNode *l);
// 尾删
int tail_del(linkNode *l);
// 按值删除,返回删除的下标
int del_by_value(linkNode *l, data value);
// 单链表的按值查找,返回下标
int find_by_value(linkNode *l, data value);
// 单链表的按值修改,返回下标
int change_by_value(linkNode *l, data old_value, data new_value);
// 单链表的按位置查找,返回对应的值
data find_by_index(linkNode *l, int index);
// 单链表的选择排序
int select_sort(linkNode *l);
// 单链表的冒泡排序
int bubble_sort(linkNode *l);
// 输出单链表
int output(linkNode *l);
// 单链表去重
int remove_duplicates(linkNode *l);
// 释放单链表
int free_linkNode(linkNode **l);
// 单链表的翻转,循环头插
int flip_linkNode(linkNode *l);
#endif
50.5.2 功能实现(linkedList.c)
#include "head.h"
// 创建单链表
linkNode *init()
{
// 申请一个结构体大小的堆区空间,用p指向
linkNode *p = (linkNode *)malloc(sizeof(linkNode));
// 申请失败时返回NULL
if (NULL == p)
{
printf("创建顺序表失败\n");
return NULL;
}
// 初始结点数为0(头结点除外)
p->len = 0;
// 初始结点的指针域指向NULL
p->next = NULL;
// 返回申请的堆区空间的首地址
return p;
}
// 单链表的判空
int is_empty(linkNode *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
// 只有一个头结点,或者结点数为0(头结点除外),此时链表为空,返回1
if (NULL == l->next && l->len == 0)
{
return 1;
}
return 0; // 链表不是空,返回0
}
// 单链表的尾插
int tail_insert(linkNode *l, data value)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点
linkNode *p = l;
// 循环向后指,直到p指向最后一个结点
while (NULL != p->next)
{
p = p->next;
}
// 新申请一个结点的空间,用ll指向
linkNode *ll = (linkNode *)malloc(sizeof(linkNode));
// 新结点的值为传进来的值
ll->num = value;
// 新结点的next指针域指向NULL
ll->next = NULL;
// 新结点插到最后
p->next = ll;
// 结点计数变量+1
l->len++;
return 0;
}
// 单链表的头插,头结点之后
int head_insert(linkNode *l, data value)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点
linkNode *p = l;
// 新申请一个结点的空间,用ll指向
linkNode *ll = (linkNode *)malloc(sizeof(linkNode));
// 新结点的值为传进来的值
ll->num = value;
// 新结点的next指针域指向头结点的原下一结点,即原第一结点
ll->next = p->next;
// 让头结点指向这个新结点
p->next = ll;
// 结点计数变量+1
l->len++;
return 0;
}
// 指定下标插入,不能是头结点
int inset_by_index(linkNode *l, int index, data value)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (index <= 0 || index > l->len)
{
printf("传入的下标有问题\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点
linkNode *p = l;
// 记录当前下标
int i = 1;
while (1)
{
// 如果当前下标与传进来的下标相等,进行头插
// 因为此时指针p已经循环指向了此下标对应的结点的首地址
if (i == index)
{
//head_insert(p, value); // 不能直接调用头插法,因为传进来的头结点不是真正头结点
linkNode *ll = (linkNode *)malloc(sizeof(linkNode));
ll->num = value;
ll->next = p->next;
p->next = ll;
// 插入后退出循环
break;
}
// 如果找到了最后还没找到,说明位置参数不对
if (NULL == p->next)
{
printf("传入位置参数不合法\n");
// 位置参数不对,return -1 表示函数异常结束
return -1;
}
// 这一圈没找到则当前下标+1向后找
++i;
// p向后指,继续向后找
p = p->next;
}
// 找到并插入后,结点数+1
l->len++;
return 0;
}
// 头删,头结点之后
int head_del(linkNode *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (is_empty(l))
{
printf("只有头结点,无法进行删除\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点
linkNode *p = l;
// 保存待删除的结点,第一结点
linkNode *q = l->next;
// 将头结点的next指针域指向下下一结点
p->next = q->next;
// 释放第一结点的空间
free(q);
q = NULL;
// 结点计数变量-1
l->len--;
return 0;
}
// 尾删
int tail_del(linkNode *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (is_empty(l))
{
printf("只有头结点,无法进行删除\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点
linkNode *p = l;
// 循环找到最后一个结点
while (1)
{
// 如果下一结点的next指针域指向NULL
// 说明当前结点是倒数第二个结点
if (NULL == p->next->next)
{
// q指向最后这个结点
linkNode *q = p->next;
// 倒数第二个结点的next指针域指向NULL
p->next = NULL;
// 释放最后这个结点的空间
free(q);
q = NULL;
// 退出循环
break;
}
// 当前结点不是倒数第二个结点时继续向下找
p = p->next;
}
// 删除后,结点计数变量-1
l->len--;
return 0;
}
// 指定下标删除,头结点之后
int del_by_index(linkNode *l, int index)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (index <= 0 || index > l->len)
{
printf("传入的下标有问题\n");
return -1;
}
if (is_empty(l))
{
printf("只有头结点\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点
linkNode *p = l;
// 记录当前下标
int i = 1;
while (1)
{
// 如果当前下标与传进来的下标相等,进行删除
// 此时指针p的next指针域已经循环指向了要找的下标对应的结点
// 即p指向要删除的结点的前一结点
if (i == index)
{
// q指向这个要删除的结点
linkNode *q = p->next;
// 让要删除的结点前一结点和后面一个结点连接
// 越过要删除的这个结点
p->next = q->next;
// 释放要删除的这一结点的空间
free(q);
q = NULL;
// 删除后退出循环
break;
}
// 循环到了最后还没找到这个下标对应的结点
if (NULL == p->next)
{
// 提示位置参数不对,并返回-1表示函数异常结束
printf("传入位置参数不合法\n");
return -1;
}
// 这一圈没找到则当前下标+1向后找
++i;
// p继续向后指,向后接着找
p = p->next;
}
// 删除后,结点计数变量-1
l->len--;
return 0;
}
// 按值删除,返回删除的下标
int del_by_value(linkNode *l, data value)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (NULL == l->next)
{
printf("只有头结点\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点
linkNode *p = l;
// 记录当前下标
int i = 1;
// 循环向后找
while (1)
{
// 如果找到了最后还没找到就退出循环
if (NULL == p->next)
{
break;
}
// 如果下一结点的值与传进来的值相同,则进行删除
if (value == p->next->num)
{
// 指针q指向下一结点,即要删除的结点
linkNode *q = p->next;
// 当前结点的next指针域指向要删除的结点的下一个结点
p->next = q->next;
// 释放要删除的结点的空间
free(q);
l->len--;
// 返回当前所在下标
return i;
}
// 这一圈没找到则当前下标+1向后找
++i;
// p继续向后指,向后接着找
p = p->next;
}
// 循环没找到,输出找不到这个值
printf("找不到这个值\n");
// 返回-1表示函数异常结束
return -1;
}
// 单链表的按值查找,返回下标,头结点之后
int find_by_value(linkNode *l, data value)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (NULL == l->next)
{
printf("只有头结点\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点的下一结点
linkNode *p = l->next;
// 记录当前位置
int i = 1;
// 循环向后找
while (1)
{
// 如果下一结点的值与传进来的值相同,则返回当前位置
if (value == p->num)
{
return i;
}
// 如果找到了最后还没找到就退出循环
if (NULL == p->next)
{
break;
}
// 这一圈没找到则当前下标+1向后找
++i;
// p继续向后指,向后接着找
p = p->next;
}
// 循环没找到,输出找不到这个值
printf("链表中没有这个值\n");
// 返回-1表示函数异常结束
return -1;
}
// 单链表的按值修改,返回下标
int change_by_value(linkNode *l, data old_value, data new_value)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (NULL == l->next)
{
printf("只有头结点\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点的下一结点
linkNode *p = l->next;
// 记录当前位置
int i = 1;
// 循环向后找
while (1)
{
// 如果下一结点的值与传进来的旧值相同,则用新值进行修改并返回当前位置
if (old_value == p->num)
{
p->num = new_value;
return i;
}
// 如果找到了最后还没找到就退出循环
if (NULL == p->next)
{
break;
}
// 这一圈没找到则当前下标+1向后找
++i;
// p继续向后指,向后接着找
p = p->next;
}
// 循环没找到,输出找不到这个值
printf("链表中没有这个值\n");
// 返回-1表示函数异常结束
return -1;
}
// 单链表的按位置查找,返回对应的值
data find_by_index(linkNode *l, int index)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (index < 0 || index > l->len)
{
printf("传入的下标有问题\n");
return -1;
}
if (NULL == l->next)
{
printf("只有头结点\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点的下一结点
linkNode *p = l->next;
// 记录当前位置
int i = 1;
// 循环向后找
while (1)
{
// 如果当前位置与传进来的下标相同,则返回这个位置的值
if (i == index)
{
return p->num;
}
// 如果找到了最后还没找到则提示位置参数不对
if (NULL == p->next)
{
printf("传入位置参数不合法\n");
// 返回-1表示函数异常结束
return -1;
}
// 这一圈没找到则当前下标+1向后找
++i;
// p继续向后指,向后接着找
p = p->next;
}
}
// 单链表的简单选择排序,降序
int select_sort(linkNode *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (NULL == l->next)
{
printf("只有头结点,无法进行简单选择排序\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点的下一结点
// 结点指针p后面表示当前结点,结点指针q后面表示被比较的的结点
linkNode *p = l->next, *q;
// 外层循环,控制循环次数
while (1)
{
// 后面没有结点了,结束外层循环
if (NULL == p->next)
{
break;
}
// 指针q指向p的下一结点
// 表示被比较的结点是从当前结点的下一结点开始,会到最后一个结点
q = p->next;
// 假设最大值的结点是当前结点
linkNode *max = p;
// 内层循环,表示比较次数
while (1)
{
// 如果被比较的结点比当前认定的最大结点的值还大
if (q->num > max->num)
{
// 那么让最大结点指针指向这个结点
max = q;
}
// 如果被比较的结点到了最后一个,则退出内层循环
if (NULL == q->next)
{
break;
}
// 被比较的结点逐个往后挪
q = q->next;
}
// 当前结点与最大结点指针指向的结点进行值交换
data t = max->num;
max->num = p->num;
p->num = t;
// 当前结点逐个往后挪
p = p->next;
}
return 0;
}
// 单链表的冒泡排序,升序
int bubble_sort(linkNode *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (NULL == l->next)
{
printf("只有头结点,无法进行冒泡排序\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点的下一结点
// 结点指针p控制外层循环次数,结点指针end控制内层比较次数
// 结点指针q用于比较
linkNode *p = l->next, *q, *end = NULL;
// 外层循环,表示循环次数
while (1)
{
if (NULL == p->next) // 后面没有结点了,直接结束
{
break;
}
// 每次都从第一个数据结点开始(头结点后的第一结点)
q = l->next;
// 内层循环,表示比比较次数
while (1)
{
// 如果 当前值 大于 后面的值,则交换
if (q->num > q->next->num)
{
data t = q->num;
q->num = q->next->num;
q->next->num = t;
}
// 到了尚未比较的结点中倒数第二个结点,前面已经比较过了,该退出进行下一轮的比较了
if (end == q->next->next)
{
// 更新结束条件
end = q->next;
break;
}
// 内层循环递进,用于比较的结点向后逐步挪移
q = q->next;
}
// 外层循环递进,p逐渐向后指向,直到最后一个结点后进入下次循环时结束
p = p->next;
}
return 0;
}
// 输出单链表
int output(linkNode *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点
// 用于指向待输出的结点
linkNode *p = l;
// 记录当前到第几个结点
int i = 0;
while (1)
{
// 是头结点时
if (0 == i)
{
printf("除头结点外,其余结点个数为:%d\n", p->len);
}
else // 非头结点时
{
printf("第%d位数据为:%d\n", i, p->num);
}
// 位置变量+1
++i;
// 如果到了最后一个结点,那么就退出(因为是先输出的,所以已经输出国了)
if (NULL == p->next)
break;
// 待输出的结点向后逐位移动
p = p->next;
}
return 0;
}
// 单链表去重
int remove_duplicates(linkNode *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点
// 用于记录当前结点
linkNode *p = l;
// 外层循环,控制循环次数
while (1)
{
// 用于记录被比较结点的前一结点
linkNode *q = p;
// 内层循环,用于控制比较次数
while (1)
{
// 如果被比较的结点到了最后一位,则退出
if (NULL == q->next)
{
break;
}
// 如果当前结点的值与被比较的结点的值相同,则删除这个被比较的结点
if (q->next->num == p->num)
{
// 用于指向被比较的结点
linkNode *t = q->next;
// 被比较的前一结点的next指针域指向被比较的结点的后一结点
q->next = t->next;
// 释放这个被比较的结点
free(t);
t = NULL;
// 结点计数变量-1
l->len--;
}
// 让被比较的结点向后挪动
q = q->next;
}
// 让当前结点向后挪动
p = p->next;
// 如果当前结点挪到了最后了,则退出
if (NULL == p)
{
break;
}
}
return 0;
}
// 释放单链表
int free_linkNode(linkNode **l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
// 用于接收传来的头结点指针
// 传来的是头结点指针的地址,所以需要解引用获得其指向的地址
linkNode *p = *l;
// 用于存放准备释放的结点的下一结点,免得找不到了
linkNode *q = p->next;
// 进入循环,准备逐个释放
while (1)
{
// 释放当前结点
free(p);
// 让下一结点变成当前结点
p = q;
// 如果当前结点到了最后,指针已经指向NULL,表示空间都释放了
// 所以需要将头结点指针的指向更改,所以需要解引用指向NULL,然后退出
if (NULL == p)
{
*l = NULL;
return 0;
}
// 待释放结点的下一结点向后挪
// 因为上面已经把原来待释放结点的下一结点变成了当前准备释放的结点了
q = q->next;
}
}
// 单链表的翻转,循环头插
int flip_linkNode(linkNode *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (NULL == l->next)
{
printf("只有头结点,无法进行翻转\n");
return -1;
}
if (1 == l->len)
{
printf("只有一个数据结点,无需翻转\n");
return -1;
}
// 让除了第一数据结点外的其他结点依次进行头插,即可得到翻转后的链表
// 如果直接调用头插法,会重新生成结点,导致空间浪费
// 这是个面试题
linkNode *p = l->next->next; // 记录当前需要头插的结点
// 将原本的第一数据结点的next指针域指向NULL
// 因为最后这个结点会变成尾结点
l->next->next = NULL;
linkNode *q = p->next; // 记录下次需要头插的结点
// 循环头插
while (1)
{
// 将当前需要插入的结点的next指针域指向现在的第一数据结点
p->next = l->next;
// 将头结点的next指针域指向当前需要插入的结点
// 这样当前需要插入的结点就完成了头插,变成了新的第一数据结点
l->next = p;
// 让下次需要头插的结点变成当前需要插入的结点
p = q;
// 如果下次需要头插的结点没有指向原尾结点的next指针域
if (NULL != q)
{
q = q->next;
}
// 如果当前下次需要插入的结点已经指向原尾结点的next指针域
// 即表示已经到了原尾结点的后一个结点了,那么就退出循环
// 这样可以将原尾结点也进行头插,不然会漏掉原尾结点
else
break;
}
return 0;
}
50.5.3 测试用主函数(main.c)
#include "head.h"
int main()
{
linkNode *l = init();
output(l);
printf("%d\n", is_empty(l));
head_insert(l, 32);
output(l);
printf("%d\n", is_empty(l));
putchar(10);
head_insert(l, 35);
output(l);
putchar(10);
tail_insert(l, 100);
output(l);
putchar(10);
head_insert(l, 32);
output(l);
putchar(10);
head_insert(l, 39);
output(l);
putchar(10);
printf("两极反转\n");
flip_linkNode(l);
output(l);
putchar(10);
printf("简单选择排序\n");
select_sort(l);
output(l);
putchar(10);
printf("冒泡排序\n");
bubble_sort(l);
output(l);
putchar(10);
printf("去重\n");
remove_duplicates(l);
output(l);
putchar(10);
del_by_index(l, 2);
// head_del(l);
output(l);
putchar(10);
printf("%d\n", find_by_index(l, 1));
printf("%d\n", find_by_value(l, 200));
output(l);
putchar(10);
printf("%d\n", del_by_value(l, 100));
output(l);
putchar(10);
printf("%p\n", l);
printf("释放\n");
free_linkNode(&l);
printf("%p\n", l);
return 0;
}
50.5.1 Makefile
- 想看注释的话,对照前面文章的,都是一个格式
EXE=linkedList
CC=gcc
CFLAGs=-c
OBJs+=linkedList.o
OBJs+=main.o
all:$(EXE)
$(EXE):$(OBJs)
$(CC) $^ -o $@
%.o:%.c
$(CC) $(CFLAGs) $^ -o $@
clean:
rm *.o linkedList