2.1单链表
单链表是链表的一种,是线性表的链式存储结构。其逻辑结构是线性的,物理结构则是随机性存储的。
习惯上用LinkList 声明头指针变量,用Node*声明节点指针变量,实际上他们是等价的。
2.2带头结点的单链表初始化函数中的注意点
函数实现如下:
void InitList(LinkList* L) /*初始化单链表*/
{ /*形参为指向表头指针的指针变量,这样才能通过传址
参数将函数结果带出*/
*L = (LinkList)malloc(sizeof(struct SListNode)); /*建立头结点*/
if ((*L) == NULL)
{
printf("\n内存分配不成功!\n");
return ERROR;
} /*考虑到内存分配失败*/
(*L)->next = NULL; /*将表头的置空*/
} /*InitList */
要注意的地方是,函数的形参应该是LinkList*类型,等价于Node**类型,它是指向指针的指针。否则函数初始化的结果无法带出,将指向头结点的指针的地址作为实参,这里是指针类型的传址调用。
加上判断malloc是否成功的语句可以解除取消对NULL指针的引用warning。
2.3建立链表时的getchar函数问题
在使用getchar函数之前要用rewind(stdin);语句清除输入缓冲区,防止读取缓冲区中残留的字符(一般是回车符)。
2.4注意指针仅用于遍历和用于改变结点时的表达式有所不同
Node* p;
p = xxx; 此时仅仅改变p内存储的地址,也就是只改变p本身。
p->xxx = xxx; 此时改变了p所指向的结点的内容。
2.5注意使用free函数释放掉malloc过的不用的空间
2.6代码实现(详细的思路和编写时犯过的错误见注释:)
/*头文件、单链表存储结构和各种操作的声明及简单说明*/
#include<stdio.h>
#include<stdlib.h>
#define ERROR -1
#define OK 1
typedef char ElemType;
typedef struct SListNode {
ElemType data;
struct SListNode* next;
}Node,*LinkList;
void InitList(LinkList *L); /*初始化单链表*/
void PrintList(LinkList L); /*打印单链表*/
void CreatFromHead(LinkList L); /*头插法建立单链表*/
void CreatFromTail(LinkList L); /*尾插法建立单链表*/
Node* GetElem(LinkList L, int i); /*在单链表中查找第i个结点*/
Node* Locate(LinkList L, ElemType key); /*在单链表中查找值为key的结点并返回其地址*/
int ListLength(LinkList L); /*求单链表的长度*/
int InsList(LinkList L, int i, ElemType e); /*在单链表中第i个位置插入值为e的新结点*/
int DelList(LinkList L, int i, ElemType* e); /*删除单链表中第i个位置的结点*/
LinkList MergeList(LinkList LA, LinkList LB);/*尾插法合并两个有序非降序单链表为升序*/
/*各种操作的具体实现和详细注释*/
#include"SLinkList.h"
void InitList(LinkList* L) /*初始化单链表*/
{ /*形参为指向表头指针的指针变量,这样才能通过传址参数将函数结果带出*/
*L = (LinkList)malloc(sizeof(struct SListNode)); /*建立头结点*/
if ((*L) == NULL)
{
printf("\n内存分配不成功!\n");
return ERROR;
} /*考虑到内存分配失败*/
(*L)->next = NULL; /*将表头的置空*/
} /*InitList */
void PrintList(LinkList L) /*打印单链表*/
{
Node* cur = L->next; /*cur指针用于遍历单链表打印*/
while (cur) /*当cur为空时,说明上一个结点的next指针为空,即单链
表遍历完成*/
{
printf("%c ", cur->data);
cur = cur->next;
} /*while */
printf("\n");
} /*PrintList */
void CreatFromHead(LinkList L) /*头插法建立单链表*/
{
Node* s = NULL;
char c; /*用于接收输入字符*/
int flag = 1; /*设置一个标志位,当输入‘$’时置0*/
rewind(stdin); /*清除输入缓冲区,防止读取缓冲区中残留的字符*/
while (flag)
{
c = getchar();
if (c != '$')
{
s = (Node*)malloc(sizeof(Node)); /*新建一个结点*/
if (s == NULL)
{
printf("\n内存分配不成功!\n");
return ERROR;
}
s->data = c; /*将数据写入新建结点*/
s->next = L->next; /*将新建结点的next指针指向原来的第一个结点*/
L->next = s; /*将头结点next指针指向新建结点*/
}
else
{
flag = 0;
}
} /*while */
} /*CreatFromHead */
void CreatFromTail(LinkList L) /*尾插法建立单链表*/
{
Node* s;
Node* cur = L; /*声明一个用于指向单链表队尾的指针*/
char c;
int flag = 1;
rewind(stdin); /*清除输入缓冲区,防止读取缓冲区中残留的字符*/
/*while (cur->next)
{
cur = cur->next;
}*/ /*尾插法建表则初始表尾就在表头*/
while (flag)
{
c = getchar();
if (c != '$')
{
s = (Node*)malloc(sizeof(Node));
if (s == NULL)
{
printf("\n内存分配不成功!\n");
return ERROR;
}
s->data = c;
cur->next = s; /*将原队尾结点的next指针指向新建结点*/
//s->next = NULL; /*如需继续插入则无须将当前结点指针域清空*/
cur = s; /*cur指针指向新的队尾*/
}
else
{
flag = 0;
cur->next = NULL; /*结束建表时将队尾结点的next指针清空*/
}
} /*while */
} /*CreateFromTail */
Node* GetElem(LinkList L, int i) /*在单链表中查找第i个结点*/
{
Node* cur = L;
int j = 0;
if (i < 1) /*查找的结点序号书必须是从1开始的连续正整数*/
{
printf("\n结点序号是从1开始的连续正整数!\n");
return ERROR;
}
//while (cur != NULL && j < i) /*终止循环条件为结点指针为空或j == i */
while (cur->next != NULL && j < i) /*终止循环条件为结点next指针为空或j == i,少查找
一次*/
{
cur = cur->next;
j++;
} /*while */
/*if (cur)
{
return cur;
}*/ /*循环跳出时cur不为空说明循环跳出时i == j即找到了第i号结点,否
则就是不存在第i号结点*/
if (i == j) /*查找到最后一个结点时i == j,说明刚好最后一个结点就是要找结
点,否则就是不存在第i号结点*/
{
return cur;
}
else
{
printf("\n查无此结点!\n");
return ERROR;
}
} /*GetElem */
Node* Locate(LinkList L, ElemType key) /*在单链表中查找值为key的结点并返回其地址*/
{
Node* cur = L;
/*while (cur->next != NULL && cur->data != key)
{
cur = cur->next;
}
if (cur->data == key)
{
return cur;
}*/
while (cur) /*遍历单链表*/
{
/*if (cur->data == key)
{
return cur;
}
cur = cur->next;*/
if (cur->data != key) /*值不相同时继续遍历,相同则返回结点地址*/
{
cur = cur->next;
}
else
{
return cur;
}
}
printf("\n单链表中没有结点的数据域存放%c!\n",key); /*遍历结束仍没有相匹配的结点,则说明
单链表中没有结点的数据域存放与key
相同的值*/
exit(ERROR);
}
int ListLength(LinkList L) /*求单链表的长度*/
{
int i = 0;
Node* cur = L;
while (cur->next != NULL) /*i随着cur一起变化,i的数值即为结点的序号(不包含头结点),
故当cur指向最后一个结点时的i值即为单链表长度*/
{
i++;
cur = cur->next;
}
return i;
} /*ListLength*/
int InsList(LinkList L, int i, ElemType e) /*在单链表中第i个位置插入值为e的新结点*/
{
int j = 0;
Node* s;
Node* prev = L;
if (i < 1)
{
printf("\n插入位置不合法!\n");
return ERROR;
}
while (prev && j < i - 1) /*找到第i个结点的前驱结点*/
{
j++;
prev = prev->next;
}
if (prev) /*如果第i个结点的前驱结点不为空*/
{
s = (Node*)malloc(sizeof(Node));
if (s == NULL)
{
printf("\n内存分配不成功!\n");
return ERROR;
}
s->data = e;
s->next = prev->next; /*将新结点的next指针指向原第i个结点*/
prev->next = s; /*将原第i个结点的前驱结点的next指针指向新结点*/
return OK;
}
printf("\n插入位置不合法!\n");
return ERROR;
}
int DelList(LinkList L, int i, ElemType* e) /*删除单链表中第i个位置的结点*/
{
int j = 0;
Node* r; /*用于保存被删除结点的地址*/
Node* prev = L;
if (i < 1)
{
printf("\n删除位置不合法!\n");
return ERROR;
}
while (prev->next && j < i - 1) /*找到第i个结点的前驱结点,注意删除结点的前驱结点的
合法范围为0到倒数第二个结点*/
{
j++;
prev = prev->next;
}
if (prev->next)
{
r = prev->next; /*保存被删除结点的地址*/
prev->next = r->next; /*修改指针,将被删除结点的先驱结点的next指针指向被删除结点的后驱结点*/
*e = r->data; /*保存一下被删除的数据*/
free(r); /*释放被删除结点占用的空间,malloc的用法*/
return OK;
}
printf("\n删除位置不合法!\n");
return ERROR;
}
LinkList MergeList(LinkList LA, LinkList LB)/*尾插法合并两个有序非降序单链表仍为有序非降序*/
{
//LinkList LC = NULL;
//LC = (LinkList)malloc(sizeof(Node));
//if (LC == NULL)
//{
// printf("\n内存分配不成功!\n");
// return ERROR;
//}
//LC->next = NULL; /*创建链表LC,初始化其next指针为空。考虑到InitList函数
对本函数可见,也可使用函数进行初始化*/
//Node* pa = LA->next;
//Node* pb = LB->next;
///*Node* pc = LC->next;*/ /*错误,这样创建无法改变LC->next的指向,也就无法辅助尾
插法创建LC链表*/
//Node* pc = LC; /*创建用于移动的指针*/
//while (pa && pb) /*遍历LA和LB,比大小后把数据域较小的结点尾插到LC中*/
//{
// if (pa->data <= pb->data)
// {
// /*pc = pa*/ /*错误,这样只改变pc的指向,而LC->next的指向一直为空*/
// pc->next = pa;
// pc = pc->next; /*尾插*/
// pa = pa->next; /*pa继续遍历LA*/
// }
// else
// {
// /*pc = pa*/ /*同样的错误*/
// pc->next = pb;
// pb = pb->next; /*尾插*/
// pc = pc->next; /*pb继续遍历LB*/
// }
//} /*while */
//if (pa)
//{
// /*pc = pa*/ /*同样的错误*/
// pc->next = pa;
//}
//if (pb)
//{
// /*pc = pb*/ /*同样的错误*/
// pc->next = pb;
//} /*将未遍历完的链表的剩余部分插到LC末尾*/
//free(LA);
//free(LB); /*释放掉LA和LB两个头结点的空间*/
//return LC; /*返回合并后的链表LC的地址*/
/*以上算法可以优化如下:*/
LinkList LC;
LC = LA;
Node* pa = LA->next;
Node* pb = LB->next;
Node* pc = LC;
while (pa && pb) /*遍历LA和LB,比大小后把数据域较小的结点尾插到LC中*/
{
if (pa->data <= pb->data)
{
pc->next = pa;
pc = pc->next; /*尾插*/
pa = pa->next; /*pa继续遍历LA*/
}
else
{
pc->next = pb;
pb = pb->next; /*尾插*/
pc = pc->next; /*pb继续遍历LB*/
}
} /*while */
if (pa)
{
pc->next = pa;
}
if (pb)
{
pc->next = pb;
} /*将未遍历完的链表的剩余部分插到LC末尾*/
free(LB); /*释放掉LB的头结点的空间,此时LA头结点就是LC头结点,所以不用释放
LA的头结点*/
return LC; /*返回合并后的链表LC的地址*/
/*优化后的算法少开辟了一个LC的头结点空间*/
} /*MergeList */
/*主函数和测试程序*/
#include"SLinkList.h"
TestSLinkList() /*自定义各种操作的测试程序*/
{
//char c;
//int num;
//LinkList LA;
//LinkList LB;
//LinkList LC;
//InitList(&LA);
//InitList(&LB);
//CreatFromHead(L);
//CreatFromTail(LA);
//CreatFromTail(LB);
//PrintList(LA);
//PrintList(LB);
//printf("\n输入要查找的结点序号:");
//scanf_s("%d", &num);
//printf("\n第%d号结点的数据域中数据元素为%c。\n", num, GetElem(L, num)->data);
//printf("\n输入要查找的数据元素:");
rewind(stdin);
//rewind(stdin); /*清除输入缓冲区*/
//c = getchar();
//printf("\n%c存放在单链表中地址为“%p”的结点的数据域中。\n",c,Locate(L,c));
//InsList(L, 3, 'c');
//DelList(L, 3, &c);
//LC = MergeList(LA, LB);
//PrintList(LC);
}
int main()
{
TestSLinkList(); /*主函数调用测试函数*/
return 0;
}