单链表不带头结点结构体设计:
//不带头结点的结构体设计:
typedef int ELEM_TYPE;
//有效数据节点结构体设计
typedef struct Node
{
ELEM_TYPE data;//数据域 (1.头结点:不保存任何数据 2.有效数据节点:保存有效值)
struct Node* next;//指针域 (1.头结点:保存第一个元素的地址 2.有效数据节点:保存下一个有效元素的地址)
}Node, * PNode;
老规矩,实现链表的基本功能:增删改查
函数声明如下:
/初始化函数(对于头结点进行赋初值)
void no_head_Init_list(struct Node** phead);//struct Node ** == PNode *
//购买一个新节点
struct Node* no_head_BuyNode(ELEM_TYPE val);
//头插
bool no_head_Insert_head(PNode* phead, ELEM_TYPE val);
//尾插
bool no_head_Insert_tail(struct Node** phead, ELEM_TYPE val);
//按位置插入(pos=0 相当于头插 pos==length 相当于尾插)
bool no_head_Insert_pos(struct Node** phead, int pos, ELEM_TYPE val);
//头删
bool no_head_Del_head(struct Node** phead);
//尾删
bool no_head_Del_tail(struct Node** phead);
//按位置删(pos==0 相当于头删 pos==length-1 相当于尾删(pos==length非法))
bool no_head_Del_pos(struct Node** phead, int pos);
//按值删
bool no_head_Del_val(struct Node** phead, ELEM_TYPE val);
//获取值位置 (如果val值存在, 则返回其地址 不然返回NULL)
struct Node* no_head_Search(struct Node** phead, ELEM_TYPE val);
//判空
bool no_head_IsEmpty(struct Node** phead);
//判满 单链表不存在满这个概念
//获取单链表有效数据节点个数
int no_head_Get_length(struct Node** phead);
//清空 相当于直接调用销毁
void no_head_Clear(struct Node** phead);
//销毁1(malloc申请来的空间 全部释放掉)
void no_head_Destroy(struct Node** phead);
//销毁2
void no_head_Destroy2(struct Node** phead);
//打印
void no_head_Show(struct Node** phead);
对于函数括号里面的参数为什么是二级指针呢?由于该单链表没有头结点,所以我们需要一个指针来指向第一个有效节点,那么为了能使函数内部的改变能够影响外部数据,所以需要传参数时传入二级指针。
1.初始化
void no_head_Init_list(struct Node** phead)//struct Node ** == PNode *
{
//assert phead!=NULL
assert(phead != NULL);
*phead = NULL;
//修改一个指针,如果没有有效节点的话,将其指针指向NULL
//这个指针保存第一个有效数据节点的地址 phead *phead(保存的第一个有效数据的地址) (*phead).next
}
2.申请一个新节点便于插入
struct Node* no_head_BuyNode(ELEM_TYPE val)
{
struct Node* pnewnode = (struct Node*)malloc(1 * sizeof(struct Node));
assert(pnewnode != NULL);
if (pnewnode == NULL)
{
return NULL;
}
pnewnode->data = val;
pnewnode->next = NULL;
return pnewnode;
}
3.头插
bool no_head_Insert_head(PNode* phead, ELEM_TYPE val)
{
//assert
assert(phead != NULL);
if (NULL == phead)
{
return false;
}
//1.创建新节点
struct Node* pnewnode = (struct Node*)malloc(1 * sizeof(struct Node));
assert(pnewnode != NULL);
pnewnode->data = val;
pnewnode->next = NULL;
//2.找到合适的插入位置 (头插函数 已经找到插入位置)
//3.插入
pnewnode->next = *phead;
*phead = pnewnode;
return true;
}
4.尾插
bool no_head_Insert_tail(struct Node** phead, ELEM_TYPE val)
{
assert(phead != NULL);
if (no_head_IsEmpty(phead))//如果我是一个空链,则执行头插函数 不要执行尾插函数
{
return no_head_Insert_head(phead, val);
}
//1.创建新节点
PNode pnewnode = (PNode)malloc(sizeof(Node) * 1);
assert(pnewnode != NULL);
pnewnode->data = val;
pnewnode->next = NULL;
//2.找到合适的插入位置
PNode p = *phead;
for (p; p->next != NULL; p = p->next);//这里存在一个bug:如果是空链的话,*phead == p == NULL,那么语句2:p->next会报写入异常(在上面处理一下这个bug即可)
//此时 指针p停留在尾结点,而不是尾结点的下一个节点
p->next = pnewnode;
return true;
}
5.按位置插
//按位置插入(pos=0 相当于头插 pos==length 相当于尾插)
bool no_head_Insert_pos(struct Node** phead, int pos, ELEM_TYPE val)
{
assert(phead != NULL);
assert(pos >= 0 && pos <= no_head_Get_length(phead));//pos == length合法 相当于尾插 但是这个位置不能用于删除
if (pos == 0)
{
return no_head_Insert_head(phead, val);
}
//1.购买新节点
PNode pnewnode = no_head_BuyNode(val);
assert(pnewnode != NULL);
//2.找到合适的位置
PNode p = *phead;
for (int i = 1; i < pos; ++i)//这里存在一个bug:如果pos = 0,p没有办法跑到合适的位置,提前处理掉即可
{
p = p->next;
}
//3.插入
pnewnode->next = p->next;
p->next = pnewnode;
return true;
}
6,。头删
bool no_head_Del_head(struct Node** phead)
{
assert(phead != NULL);
if (no_head_IsEmpty(phead))//如果是空链,返回假
{
return false;
}
struct Node* p = *phead;
*phead = p->next;
free(p);
return true;
}
7.尾删(不带头结点的单链表尾删较为麻烦,会有两种风险)
bool no_head_Del_tail(struct Node** phead)
{
assert(phead != NULL);
//两种风险 第一种:q有可能为NULL(由于空链导致)
if (no_head_IsEmpty(phead))
{
return false;
}
//什么情况下:q不为NULL,但是q->next为NULL? //有节点,但是仅有一个节点
//两种风险 第二种:q->next有可能为NULL(由于导致)
if ((*phead)->next == NULL) //仅有一个首元素节点
{
return no_head_Del_head(phead);//仅有一个节点,尾删和头删没区别
}
PNode q = *phead;
for (q; q->next->next != NULL; q = q->next);//q向后一次项探测两步(有bug风险:1.p可能为NULL 2.p->next为NULL)
//此时q停在倒数第二个节点(尾删的待删除节点的上一个节点)
struct Node* p = q->next;//p通过q来直接指向待删除节点
q->next = p->next;
free(p);
return true;
}
8.按位置删
//按位置删(pos==0 相当于头删 pos==length-1 相当于尾删(pos==length非法))
bool no_head_Del_pos(struct Node** phead, int pos)
{
assert(phead != NULL);
assert(pos >= 0 && pos < no_head_Get_length(phead));
if (no_head_IsEmpty(phead))//判空
{
return false;
}
if (pos == 0)
{
return no_head_Del_head(phead);
}
struct Node* q = *phead;
for (int i = 1; i < pos; i++)
{
q = q->next;
}
struct Node* p = q->next;
q->next = p->next;
free(p);
return true;
}
9.按值删
bool no_head_Del_val(struct Node** phead, ELEM_TYPE val)
{
assert(phead != NULL);
if ((*phead)->data == val)//判断首元素的值 如果 == val
{
return no_head_Del_head(phead);
}
PNode p = no_head_Search(phead,val);
if (p == NULL)
{
return false;
}
PNode q = *phead;
for (q; q->next != p; q = q->next);;//存在一个bug:如果要删除的值就是首元素的值,这时,q没有合适的位置去指向,所以在开头加上处理
//此时 p指向待删除节点
//q应该指向p的上一个节点
q->next = p->next;
free(p);
return true;
}
10.获取值得位置
//获取值位置 (如果val值存在, 则返回其地址 不然返回NULL)
struct Node* no_head_Search(struct Node** phead, ELEM_TYPE val)
{
assert(phead != NULL);
for (struct Node* p = *phead; p != NULL; p = p->next)
{
if (p->data == val)
{
return p;
}
}
return NULL;
}
11.判空
12.获取单链表有效节点的个数
//判空
bool no_head_IsEmpty(struct Node** phead)
{
assert(phead != NULL);
return (*phead) == NULL;
}
//判满 单链表不存在满这个概念
//获取单链表有效数据节点个数
int no_head_Get_length(struct Node** phead)
{
assert(phead != NULL);
int cout = 0;
for (PNode p = *phead; p != NULL; p = p->next)
{
cout++;
}
return cout;
}
13.清空、销毁、打印(销毁给出两种方法)
//清空 相当于直接调用销毁
void no_head_Clear(struct Node** phead)
{
no_head_Destroy(phead);
}
//销毁1(malloc申请来的空间 全部释放掉)
void no_head_Destroy(struct Node** phead)
{
assert(phead != NULL);
while (*phead != NULL)
{
PNode p = (*phead);
*phead = p->next;
free(p);
}
*phead = NULL;
}
//销毁2
void no_head_Destroy2(struct Node** phead)
{
assert(phead != NULL);
struct Node* p = *phead;
struct Node* q;
*phead = NULL;
while (p != NULL)
{
q = p->next;
free(p);
p = q;
}
}
//打印
void no_head_Show(struct Node** phead)
{
assert(phead != NULL);
for (PNode p = *phead; p != NULL; p = p->next)
{
printf("%d ", p->data);
}
printf("\n");
}
接下来在主函数里测试一下
int main()
{
struct Node* head;//头指针
no_head_Init_list(&head);//初始化
for (int i = 0; i < 20; i++)
{
no_head_Insert_pos(&head, i, i + 1);
}//将1到20分别按位置插入
no_head_Show(&head);//打印
no_head_Insert_head(&head, 100);//头插100
no_head_Insert_tail(&head, 200);//尾插200
no_head_Show(&head);//打印
printf("length = %d\n", no_head_Get_length(&head));//打印单链表有效数据节点的长度
no_head_Del_head(&head);//头删
no_head_Del_tail(&head);//尾删
no_head_Show(&head);//打印
no_head_Del_pos(&head, 4);//将第4个有效节点删除
no_head_Del_val(&head, 14);//将数据为14的节点删除
no_head_Show(&head);//打印
printf("length = %d\n", no_head_Get_length(&head));//打印单链表有效数据节点的长度
//Destroy(&head);
no_head_Destroy2(&head);//销毁
//printf("%d\n", sizeof(struct Node));
return 0;
}
结果: