一,单链表的定义
单链表:线性表的链式存储,它是通过一组任意的存储单元来存储线性表中的数据元素。
单链表使用结点来存放数据元素和指向下一个结点的指针 ,结点用结构体来表示。
其中data为数据域,存放数据元素;next为指针域,存放其后继节点的地址。
代码如下:
#define ElemType int
typedef struct LNode{
ElemType data; //数据域
struct LNode *next;//指针域
}LNode,*LinkList;
此处的 LNode 和 LinkList 是有各自作用的, 在下面的代码中会看到LNode * 和 LinkList两种形式,基础好的会发现两者都一样,没错,类型确实一样,但含义不同,LinkList强调这是一个单链表,LNode * 强调这是一个结点。
还有一点,我提前说明,其实学完后可以通俗上理解,带头结点的有数据的单链表L中,L在代码中,其实就代表头结点的地址;不带头结点的有数据的单链表L中,L在代码中,其实就代表首结点的地址。当然考试中一定要具体题目 具体分析。
二,单链表的代码定义(以下均为c语言的实现)
一,头插法(带头结点和不带头结点)
//带头结点
LinkList List_HeadInsert(LinkList *L){
//先初始化链表
(*L)=(LNode*)malloc(sizeof(LNode));
(*L)->next=NULL;
//声明需要的相关变量
ElemType x;
LNode *s;
scanf("%d",&x);
while(x!=9999){
s=(LNode*)malloc(sizeof(LNode));
s->data=x;
s->next=(*L)->next;
(*L)->next=s;
scanf("%d",&x);
}
return *L
}
//不带头结点
LinkList List_HeadInsert(LinkList *L){
//先初始化链表
(*L)=NULL;
//声明需要的相关变量
ElemType x;
LNode *s;
scanf("%d",&x);
while(x!=9999){
s=(LNode*)malloc(sizeof(LNode));
s->data=x;
s->next=NULL;
if(NULL==(*L)){
(*L)=s;
}else{
s->next=(*L);
(*L)=s;
}
scanf("%d",&x);
}
return *L
}
二,尾插法(带头结点和不带头结点)
//带头结点
LinkList List_TailInsert(LinkList *L){
//初始化单链表
(*L)=(LNode*)malloc(sizeof(LNode));
LNode *p=(*L); //声明始终需要指向尾结点的结点
LNode *s; //声明插入的结点
Ex; //插入的元素值
scanf("%d",&x);
while(x!=9999){
s=(LNode*)malloc(sizeof(LNode));
s->data=x;
p->next=s; //将s插到p后面
p=s; //p始终指向当前最后一个结点
scanf("%d",&x);
}
p->next=NULL; //声明最后一个结点后为NULL
return *L;
}
//不带头结点
LinkList List_TailInsert(LinkList *L){
//初始化单链表
(*L)=NULL;
LNode *p; //声明始终需要指向尾结点的结点
LNode *s; //声明插入的结点
ElemType x; //插入的元素值
scanf("%d",&x);
while(x!=9999){
s=(LNode*)malloc(sizeof(LNode));
s->data=x;
s->next=NULL;
if(NULL==(*L)){
(*L)=s;
p=*L;
}else{
p->next=s; //将s插到p后面
p=s; //p始终指向当前最后一个结点
}
scanf("%d",&x);
}
p->next=NULL; //声明最后一个结点后为NULL
return *L;
}
三,初始化单链表
初始化不带头结点的单链表
bool InitList(LinkList *L){
*L = NULL;
return true;
}
//初始化带头结点的单链表
bool InitList(LinkList* L) { //c语言语法中,这里是一个双指针,,在下面内存分配中,需要先解引用(*L)
(*L) = (LNode*)malloc(sizeof(LNode));
if (*L == NULL) {
return false;
}
(*L)->next = NULL;
return true;
}
四,判断单链表是否为空
判断不带头结点的单链表是否为空
bool Empty(LinkList *L) {
return (L == NULL);
}
//判断带头结点的单链表是否为空
bool Empty(LinkList *L) {
return ((*L)->next == NULL);
}
五,后插入结点
//在一个结点后插入一个新结点
bool InsertNextNode(LNode* p, ElemType e) {
if (p == NULL) {
return false;
}
LNode* s = (LNode*)malloc(sizeof(LNode));
if (s == NULL) {
return false;
}
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
六,前插入结点
//在一个结点前插入一个新结点
//常规操作是先扫描全表,找到结点的前驱结点,然后在插入,此方法时间复杂度为O(n);
//而下面的方法,为:偷天换日,交换结点之间的值(data),此方法时间复杂度为O(1);
bool InsertPriorNode(LNode* p, ElemType e) {
if (p == NULL) {
return false;
}
LNode* s = (LNode*)malloc(sizeof(LNode));
if (s == NULL) {
return false;
}
s->next = p->next; //先将s结点插到p结点后面
p->next = s;
s->data = p->data; //交换s和p结点的值
p->data = e;
return true;
}
七,按位序插入
//按位序插入(不带头结点)
//在第i个位置上插入e
//平均时间复杂度为O(n)
bool ListNoHeadInsert(LinkList* L, int i, ElemType e) {
if (i < 1) {
return false;
}
if (i == 1) { //头结点需要特别插入
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = *L;
*L = s;
return true;
}
LNode* p; //指针p表示当前扫描到的那个结点
int j = 1;//表示当前指向第几个结点
p = (*L); //p指向第一个结点(不是头结点)
while (p != NULL && j < i - 1)//p->next != NULL 不能只使用这个,否则i合不合法就不能知道了
{
p = p->next;
j++;
}
if (p == NULL) {
printf("i不合法\n");
return false;
}
LNode* s = (LNode*)malloc(sizeof(LNode)); //创建一个结点
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
//按位序插入(带头结点)
//在第i个位置上插入e
//平均时间复杂度为O(n)
bool ListInsert(LinkList* L,int i,ElemType e) {
if (i < 1) {
return false;
}
LNode* p; //指针p表示当前扫描到的那个结点
int j = 0;//表示当前指向第几个结点
p = (*L); //指向头结点 ,头结点是第0个结点(不存储数据)
while (p != NULL && j < i - 1)//p->next != NULL 不能只使用这个,否则i合不合法就不能知道了
{
p = p->next;
j++;
}
//if (p == NULL) {
// printf("i不合法\n");
// return false;
//}
//LNode* s = (LNode*)malloc(sizeof(LNode)); //创建一个结点
//if (s == NULL) {
// return false;//防止内存分配失败
//}
//s->data = e;
//s->next = p->next;
//p->next = s;
//return true;
bool m = InsertNextNode(p, e);
return m; //调用另一个函数 (封装)
}
八,按位序删除一个结点
//按位序删除一个结点(带头结点)
bool ListDelete(LinkList* L, int i, ElemType *e) {
if (i < 1) {
return false;
}
LNode* p;
int j = 0;
p = *L;
while (p!=NULL && j<i-1) { //要删除第i个结点,就要找到第i-1个结点
p = p->next;
j++;
}
if (p == NULL) {
return false;
}
if (p->next == NULL) { //确认p后面有结点删
return false;
}
LNode* q = p->next; //q指向要删除的结点
*e = q->data; //返回q的data
p->next = q->next;
free(q);
return true;
}
九,删除一个结点
//直接删除一个结点
//交换数据,时间复杂度为 O(1)
bool DeleteNode(LNode *p) {
if (p == NULL) {
return false;
}
LNode* q = p->next;
p->data = p->next->data;
p->next = q->next;
return true;
}
十,按位查找按值查找
//按位查找,返回第i个结点 (带头结点)
LNode* GetElem(LinkList *L,int i) {
if (i < 0) {
return NULL;
}
int j = 0;
LNode* p;
p = *L;
while (p != NULL && j < i) {
p = p->next;
j++;
}
return p;
}
//按值查找
LNode* LocateElem(LinkList *L,ElemType e) {
LNode* p = *L;
while (p!=NULL && p->data!=e) { //此处ElemType是int类型,如果此处是结构体的话,需要写一个函数进行比较
p = p->next;
}
return p; //找到后返回结点的指针,否则返回NULL
}
十一,求长度
//求单链表的长度(带头结点)
int Length(LinkList* L) {
int len = 0;
LNode* p = *L;
while (p->next!=NULL) {
p = p->next;
len++;
}
return len;
}
十二,输出单链表
//输出单链表(带头结点)
void PrintList(LinkList* L) {
LNode* p = (*L);
while (p->next != NULL) {
p = p->next;
printf("%d ",p->data);
}
printf("\n");
}
//输出单链表(不带头结点)
void PrintListNoHead(LinkList* L) {
LNode* p = (*L);
while (p != NULL) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
三,单链表对于整个链表来说是基础非常重要