主要掌握带头结点的,不带头结点的偏难!!!
1. 不带头结点的单链表如下图
2. 结构体设计
no_head_list.h文件
#pragma once //防止头文件重复
typedef int ELEM_TYPE;
typedef struct Node
{
ELEM_TYPE data;//数据域(1.头结点:不保存任何数据 2.有效数据节点:保存有效值)
struct Node* next;//指针域(1.保存第一个元素的地址 2.有效数据节点:保存下一个有效数据节点的地址)
}Node, *PNode;
//不带头结点的单链表操作函数---增删改查
//初始化函数,头结点的数据成员全部赋初值
void nohead_Init_list(struct Node** phead); //PNode* plist==struct Node**plist
//购买一个新节点,记得只要是插入函数就得购买一个新节点
struct Node* nohead_BuyNode(ELEM_TYPE val);
//头插 ,是否成功,成功返回真,失败返回假,因此用bool类型
bool nohead_Insert_head(PNode* phead, ELEM_TYPE val);
//尾插
bool nohead_Insert_tail(struct Node** phead, ELEM_TYPE val);
//按位置插入
bool nohead_Insert_pos(struct Node** phead, int pos, ELEM_TYPE val);//pos=0就是头插,pos=length就是尾插
//头删
bool nohead_Del_head(struct Node** phead);
//尾删
bool nohead_Del_tail(struct Node** phead);
//按位置删 pos=0相当于头删,pos=length非法,只能pos=length-1就是尾删
bool nohead_Del_pos(struct Node** phead, int pos);
//按值删除
bool nohead_Del_val(struct Node** phead, ELEM_TYPE val);
//获取值位置,如果值存在,返回其地址,不然返回空
struct Node* nohead_Search(struct Node** phead, ELEM_TYPE val);
//判空
bool nohead_Isempty(struct Node** phead);
//判满,单链表不需要这个,因为用malloc申请,无限空间
//获取单链表有效数据结点个数
int nohead_Getlength(struct Node** phead);
//清空相当于直接销毁
void nohead_Clear(struct Node** phead);
//销毁1 malloc申请来的空间,全部释放掉
void nohead_Destroy1(struct Node** phead);
//销毁2
void nohead_Destroy2(struct Node** phead);
//打印
void nohead_Show(struct Node** phead);
no_head_list.cpp文件
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include"no_head_list.h"
//访问二级指针,通过解引用变成一级指针再修改其中的东西,一级指针取地址升一级变为二级指针
void nohead_Init_list(struct Node** phead) //PNode* plist==struct Node**plist
{
assert(phead != NULL);
if (phead == NULL)
{
return;
}
*phead = NULL; //不能用(*phead).next,这个只代表的是指针域,初始化是将整个链表置空
//修改一个指针,如果没有有效节点的话,将其指针指向空
//这个指针保存第一个有效数据节点的地址 phead->二级指针 *phead->一级指针,保存第一个有效数据的地址
}
//购买一个新节点,记得只要是插入函数就得购买一个新节点
struct Node* nohead_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;
}
//头插 ,是否成功,成功返回真,失败返回假,因此用bool类型
bool nohead_Insert_head(PNode* phead, ELEM_TYPE val)
{
assert(phead != NULL);
if (phead == NULL)
{
return false;
}
//创建新节点
struct Node* pnewnode = (struct Node*)malloc(1 * sizeof(struct Node));
assert(pnewnode != NULL);
if (pnewnode == NULL)
{
return false;
}
pnewnode->data = val;
pnewnode->next = NULL;
//找到合适的插入位置
//插入
pnewnode->next = *phead;
*phead = pnewnode;
return true;
}
//尾插
bool nohead_Insert_tail(struct Node** phead, ELEM_TYPE val)
{
assert(phead != NULL);
if (phead == NULL)
{
return false;
}
//判断是否为空链,如果单链表是一个空链,那么对它执行尾插操作,可以直接替换成进行头插操作,即尾插函数
if (nohead_Isempty(phead))
{
return nohead_Insert_head(phead, val);
}
//创建新节点
struct Node* pnewnode = nohead_BuyNode(val);
assert(pnewnode != NULL);
if (pnewnode == NULL)
{
return false;
}
//找到合适插入位置
struct Node* p = *phead;//指向有效数据结点的首元素地址
for (p; p->next != NULL; p = p->next);//此时,指针p停留在尾节点,必须是有效数据节点存在,不存在的话,语句2会报错
//插入
pnewnode->next = p->next;
p->next = pnewnode;
return true;
}
//按位置插入
bool nohead_Insert_pos(struct Node** phead, int pos, ELEM_TYPE val)
{
assert(phead != NULL);
if (phead == NULL)
{
return false;
}
assert(pos >= 0 && pos <= nohead_Getlength(phead));
if (pos == 0)
{
return nohead_Insert_head(phead, val);
}
//创建新节点
struct Node* pnewnode = nohead_BuyNode(val);
assert(pnewnode != NULL);
if (pnewnode == NULL)
{
return false;
}
//找到合适插入位置
struct Node* p = *phead;//pos=0,头插
for (int i = 1; i < pos; i++)
{
p = p->next;
}
//插入
pnewnode->next = p->next;
p->next = pnewnode;
return true;
}
//头删
bool nohead_Del_head(struct Node** phead)
{
assert(phead != NULL);
if (phead == NULL)
{
return false;
}
if (nohead_Isempty(phead))
{
return false;
}
//先排除空链的可能性
struct Node* p = *phead;
*phead = p->next; //如果是空链,存在bug:p->next 出错
free(p);
return true;
}
//尾删
bool nohead_Del_tail(struct Node** phead)
{
assert(phead != NULL);
if (phead == NULL)
{
return false;
}
//两种风险:第一种 q有可能为NULL(由于空链导致)
if (nohead_Isempty(phead))
{
return false;
}
//第二种风险:q->next有可能为NULL (有节点,但是只有一个节点)
if ((*phead)->next == NULL)//仅有一个元素节点,头删和尾删一样
{
return nohead_Del_head(phead);
}
//从q->next->next去找p和q的位置
struct Node* q = *phead;
for (q; q->next->next != NULL; q = q->next);//q一次性向后探测两步
struct Node* p = q->next;//p通过q来直接指向待删除节点
q->next = p->next;
free(p);
return true;
}
//按位置删 pos=0相当于头删,pos=length非法,只能pos=length-1就是尾删
bool nohead_Del_pos(struct Node** phead, int pos)
{
assert(phead != NULL);
if (phead == NULL)
{
return false;
}
assert(pos >= 0 && pos <= nohead_Getlength(phead));
if (nohead_Isempty(phead))
{
return false;
}
if (pos == 0)//触发异常,特殊处理
{
return nohead_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;
}
//按值删除
bool nohead_Del_val(struct Node** phead, ELEM_TYPE val)
{
if ((*phead)->data == val)
{
return nohead_Del_head(phead);
}
struct Node* p = nohead_Search(phead, val);
if (p == NULL)
{
return false;
}
//此时,p指向待删除节点
//q应该指向p的上一个节点
struct Node* q = *phead;
for (q; q->next != p; q = q->next);
q->next = p->next;
free(p);
return true;
}
//获取值位置,如果值存在,返回其地址,不然返回空
struct Node* nohead_Search(struct Node** phead, ELEM_TYPE val)
{
struct Node* p = *phead;
for (p; p != NULL; p = p->next)
{
if (p->data == val)
{
return p;
}
}
return NULL;
}
//判空
bool nohead_Isempty(struct Node** phead)
{
return *phead == NULL;
}
//判满,单链表不需要这个,因为用malloc申请,无限空间
//获取单链表有效数据结点个数
int nohead_Getlength(struct Node** phead)
{
int count = 0;
struct Node* p = *phead;
for (p; p != NULL; p = p->next)
{
count++;
}
return count;
}
//清空相当于直接销毁
void nohead_Clear(struct Node** phead)
{
nohead_Destroy1(phead);
}
//销毁1 malloc申请来的空间,全部释放掉
void nohead_Destroy1(struct Node** phead)
{
while (*phead != NULL)
{
struct Node* p = *phead;
*phead = p->next;
free(p);
}
*phead = NULL;
}
//销毁2
void nohead_Destroy2(struct Node** phead)
{
struct Node* p = *phead;
struct Node* q;
*phead = NULL;
while (p != NULL)
{
q = p->next;
free(p);
p = q;
}
}
//打印
void nohead_Show(struct Node** phead)
{
struct Node* p = *phead;
for (p; p != NULL; p = p->next)
{
printf("%d ", p->data);
}
printf("\n");
}
no_head_list.cpp主函数文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include"no_head_list.h"
int main()
{
struct Node *head;
nohead_Init_list(&head); //初始化
for (int i = 0; i < 20; i++)
{
nohead_Insert_pos(&head, i, i + 1); //插入值1~20
}
nohead_Show(&head);
printf("length = %d\n", nohead_Getlength(&head));
nohead_Insert_head(&head, 100); //头插
nohead_Show(&head);
nohead_Insert_tail(&head, 200); //尾插
nohead_Show(&head);
printf("length = %d\n", nohead_Getlength(&head));
nohead_Insert_pos(&head, 4, 28);
nohead_Show(&head);
nohead_Del_head(&head); //头删
nohead_Show(&head);
nohead_Del_tail(&head); //尾删
nohead_Show(&head);
printf("length = %d\n", nohead_Getlength(&head));
nohead_Del_pos(&head, 4); //按位置删除
nohead_Show(&head);
printf("length = %d\n", nohead_Getlength(&head));
nohead_Del_val(&head, 14); //按值删除
nohead_Show(&head);
printf("length = %d\n", nohead_Getlength(&head));
nohead_Destroy1(&head);
return 0;
}
运行结果:
总结:不带头结点的单链表比带头结点的单链表难,主要是在于处理一些小细节,比如插入,删除要判断有效数据节点是否存在,或者有效数据节点有且仅有一个,这些在写时要特别注意!!!