线性表的链式表示(单链表)
这是在学习数据结构时为方便自己学习个人总结的代码,水平有限,大佬勿喷,有问题欢迎一起讨论.
有很多优化的地方没有写,只有记录一下,总体感觉有点乱,有空在总结下吧!
/* Note:Your choice is C IDE */
#include "stdio.h"
#include "stdlib.h"
/*=======单链表的优缺点================
优点:不要求使用大片的连续空间,改变容量方便
缺点:不支持随机存取,存储密度低,查找结点效率低
======================================*/
/*=======带头结点V.S不带头结点======================
头节点和头指针:不管是否带有头结点,头指针都始终指向链表
的第一个结点
两个优点:由于第一个数据结点的位置比存放在头结点的指针域
封装基本操作:避免重复代码,简洁,容易维护
==================================================*/
/*
头插法:
尾插法:在插入的过程中建立一个结点指向尾结点,
每次新添加元素将该元素指向s-data=x;r->next=s;r=s;
可以将时间复杂度变为O(n)
不带头指针的插入操作比较繁琐,尤其是尾插法,
每次需要遍历链表找到尾节点,然后执行插入操作
*/
#define false 0
#define true 1
#define Maxsize 50
typedef char Elemtype;
int menu();
/*创建一个链表结点类型的结构体*/
typedef struct node{
//定义每个结点中的数据
Elemtype data;
//定义一个指向下一个结点的指针
struct node *next;
//Lnode *L == linklist L 为方便理解
}Lnode,*linklist;
//链表一般用linklist定义
//单个结点一般用Lnode
//带头结点单链表的初始化
linklist Initlist(){
Lnode *L;
L = (Lnode *)malloc(sizeof(Lnode));
if(L == NULL){
printf("内存申请失败\n");
return false;
}
//优化可以利用这个结点的数据域存放链表长度
L->next = NULL; //头节点之后暂时没有结点
printf("初始化成功\n");
return L;
}
//判空函数
int Empty(linklist L){
if(L->next == NULL)
return true;
else
return false;
}
/*不带头结点插入在1位置应该特殊处理*/
/*
if(i==1){插入在第一个结点与其他操作不同
Lnode *s = (Lnode *)malloc(sizeof(Lnode));
if(s == NULL){
printf("内存申请失败\n");
return false;
}
s->data = e;
s->next = L;
L = s;
return true;
}*/
//按位序插入(带头结点)在表中的第i个位置插入元素e
int ListInsert(linklist L,int i,Elemtype e){
//插入位置不合法
Lnode *p=L;
Lnode *q;
int j=0;
if(i<1){
return false;
}
//这个是i-1是找插入结点的前驱,在GetElem()中是查找该位置
while(p != NULL && j<i-1){
p = p->next;
j++;
}
if(p == NULL){
printf("没有找到该位置\n");
return false;
}
q = (Lnode *)malloc(sizeof(Lnode));
q->data = e;
//下面这两句顺序不能相反
q->next = p->next;
p->next = q;
printf("插入成功\n");
return true;
}
//后插操作:在结点P之后进行插入元素e
int InsertNextNode(Lnode *p,Elemtype e){
//首先进行插入位置判断
Lnode *s;
if(p==NULL){
return false;
}
s = (Lnode *)malloc(sizeof(Lnode));
if(s == NULL)
return false;
s->data = e;
s->next = p->next;
p->next = s; //将s结点链接在p之后
return true;
}
/*
在p结点之前进行插入:
两种思路:1、传入单链表的头指针,
然后进行遍历找到要p结点前一个结点q,
在q结点进行后插入操作
2、1·直接进行新建结点s,
2·将p的指针域 赋给 s,
3·将p的指针域 指向 s,
4·之后将将p结点的数据赋给s的data域
5·要插入的数据付给p结点的data域
*/
//这是2的代码实现(时间复杂的为O(1))
int InsertPeriorNode(Lnode *p,Elemtype e){
Lnode *s;
if(p==NULL){
printf("传入的结点p为空\n");
return false;
}
s = (Lnode *)malloc(sizeof(Lnode));
if(s == NULL){
printf("内存申请失败\n");
return false;
}
//注意以下顺序千万不能弄错
s->next = p->next;
p->next = s;
s->data = p->data;
p->data = e;
printf("前插成功");
return true;
}
//指定结点的删除 1、遍历找到p结点的前一个,然后进行删除操作
//2、结点s为p的后继,将s的data赋给p,然后将s的next域给p的next域,将s释放
int DeleteNode(Lnode *p){
Lnode *s;
if(p==NULL){
printf("结点为空,删除失败\n");
return false;
}
s = p->next;
//这一段代码有bug 如过p为最后一个结点
//则s=NULL为空,没有data域,这种情况只能用方法1
p->data = s->data;
p->next = s->next;
free(s); //结点释放
return true;
}
//这个这个函数完全满足i=0以及i大于链表长度
//即通过返回值是不是NULL即可判断是否查找成功
Lnode * GetElem(linklist L,int i){
Lnode *p=L;
int j=0;
if(i<0){
return NULL;
}
while(p!=NULL && j<i){
p = p->next;
j++;
}
return p;
}
//按值查找 找到数据域==e的结点
Lnode *LocateElem(linklist L,Elemtype e){
Lnode *p = L->next;
//从第一个结点开始查找数据域为e的结点
while(p != NULL && p->data == e){
p = p->next;
}
//如果是比较两个struct类型是否相等
return p;
}
//不带头结点的初始化
int Nhead_Initlist(linklist L){
L = NULL; //空表,暂时没有任何结点
return true;
}
//不带头结点的判空函数
int Nhead_Emptylist(linklist L){
if(L == NULL)
return true;
else
return false;
}
void AddHead(Elemtype d,linklist L)
{
Lnode *p = (Lnode *)malloc(sizeof(Lnode));
p->data = d;
p->next = L; //新节点的后继
L = p; //首结点
}
linklist NHeadInsert(linklist L){
int x;
Lnode *s;
L=(linklist)malloc(sizeof(Lnode));
scanf("%d",&x);
L->data=x;
L->next=NULL;
while(x!=9999){
s=(Lnode*)malloc(sizeof(Lnode));
scanf("%d",&x);
s->data=x;
s->next=L;
L=s;
}
return L;
}
linklist NoTailInsert(linklist L){
Elemtype x;Lnode *s,*r=L;
L=(linklist)malloc(sizeof(Lnode));
scanf("%c",&x);
L->data=x;
L->next=NULL;
while(x!=9999){
s=(Lnode*)malloc(sizeof(Lnode));
scanf("%c",&x);
s->data=x;
r->next=s;
r=s;
}
r->next=NULL;
return L;
}
//链表逆置
void converse(linklist L)
{
Lnode *p,*q;
//p为头结点
p=L->next;
//将链表指控(相当于切断)
L->next=NULL;
//再往链表L中进行头插逆置
while(p)
{
//此时的p是头结点,赋给q
q=p;
//将p指向下一个结点
p=p->next;
/*头插法--一直往L结点之后那个位置插入*/
q->next=L->next;
//将q结点插入L之后
L->next=q;
}
}
linklist L;
void main()
{
int acount;int i;
Elemtype str;Lnode *q;
printf("***********************************\n");
printf(" 欢迎使用单链表操作步骤\n");
printf("***********************************\n");
while(1)
{
switch(menu())
{
case 1:
L = Initlist();break;
case 2:
printf("请输入插入元素的个数:");
scanf("%d",&acount);
printf("请输入要插入的值:");
fflush(stdin);
for(i=0;i<acount;i++){
scanf("%c",&str);
//这个时间复杂度为O(n2)
ListInsert(L,i+1,str);
}
for(i=0,q=L->next;q != NULL;i++,q=q->next)
printf("%c",q->data);
printf("\n");
break;
case 3:
printf("请输入要删除的位置:");
scanf("%d",&acount);
//调用查找函数,找到位置结点
q = GetElem(L,acount);
if(q == NULL){
printf("位置结点不存在或为空\n");
break;
}
//调用删除函数,删除结点
if(DeleteNode(q))
printf("删除成功\n");
else
printf("删除失败\n");
break;
case 4:
//等待优化
printf("4");break;
case 5:
//等待优化
printf("5");break;
case 6:
//链表逆置
converse(L);break;
case 7:
for(i=0,q=L->next;q != NULL;i++,q=q->next)
printf("%c",q->data);
printf("\n");
break;
case 8:
printf("感谢使用,再见!\n");
exit(0);
}
}
}
int menu()
{
char str[5];
int select;
printf("\n\n请选择数字进行操作\n");
printf("1.单链表初始化\n");
printf("2.单链表尾插法插入\n");
printf("3.单链表按位删除\n");
printf("4.单链表结点删除\n");
printf("5.单链表按值查找\n");
printf("6.单链表逆置\n");
printf("7.单链表遍历\n");
printf("8.退出\n");
printf("请选择对应数字1--8:");
while(1)
{
//清空缓存
fflush(stdin);
gets(str);
//字符转
select=atoi(str);
if(select<1||select>8)
printf("输入错误,请重新输入:");
else
break;
}
return select;
}
2021-5-17数据结构有点难(而已😎)