头结点和头指针的区别
注意:头结点里面不放数据,从a1开始才放的数据
因为L(头指针)指向的是头结点,所以就算链表为空,头结点还存在,L就不是空指针
typedef int ElemType;
//定义结点的结构体
typedef struct LNode
{
ElemType data;
LNode* next;//指向下一个结点
}LNode,*LinkList;
这里使用的C语言的语法。LinkList等价于LNode *
//头插法创建链表
LinkList CreatList1(LinkList& L) {
LNode* s;//用来后面创建结点
int x;//用来后面接收结点的数据
L = (LinkList)malloc(sizeof(LNode));//带头结点的链表
L->next = NULL;
cin >> x;
//输入 3 4 5 6 7 9999(9999代表着结束)
while (x!=9999)
{
s = (LNode*)malloc(sizeof(LNode));//申请一个新的空间。注意强转为 LNode *
s->data = x;//将读取到的值,给新空间中的data成员
s->next = L->next;
L->next = s;
cin >> x;
}
return L;
}
写完后调试查看内存情况,输入 3 4 6 7 9999
因为是头插法,所以越早插入的值越靠后
打印的方法说明。
函数里使用了L = L -> next,但是参数并不是引用的写法,所以不会修改链表本身。L是头结点没有数据,所以开始要让它等于第一个有数据的结点,然后依次遍历,直到等于NULL
//打印链表
void PrintList(LinkList L) {
L = L->next;
while (L != NULL) {
cout << L->data << " ";
L = L->next;
}
cout << endl;
}
使用尾插法创建链表的时候需要创建一个尾结点的指针,告诉我们链表的尾部在哪,方便插入
因为新建链表,所以尾结点和头结点相同。如果是存在数据的链表,需要判断尾结点在哪
注意:插入完成后,记得把尾结点的next指向为NULL!!!
//尾插法创建新的链表
LinkList CreatList2(LinkList& L) {
L = (LinkList)malloc(sizeof(LNode));
int x;//用来接收新结点的数据
LNode* s;//用来接收新添加的结点
LNode* r = L;//r代表链表表尾结点,指向链表尾部。!因为是新建链表,所以尾节点和头结点相同!
cin >> x;
while (x != 9999) {
s = (LNode *)malloc(sizeof(LNode));
s->data = x;
r->next = s;//让尾节点r指向新加入的结点
r = s;//此时s变为新的尾节点了,更新r,使r变成最新的尾节点
cin >> x;
}
r->next = NULL;//尾节点的next指针赋值为NULL
return L;
}
如果没有将尾结点的next指向为NULL。会报如下错误
原因是打印函数PrintList循环遍历输出,遇到data值为null的时候停止,但是创建的时候没有将尾结点的next变为NULL,那么打印函数打印完尾结点后继续打印下一个结点。但是,下一个结点没有数据,存的是默认的0xcdcdcdcd,微软将这个地址设为不可读,就会出现读取访问权限冲突
这里需要注意的是,要创建一个结点指针变量来遍历每一个结点,接收遍历到的结点的值。
但是头结点没有data值,所以要让它从头结点的下一个结点(第一个有值的结点)开始遍历
//按序号查找结点值
LNode* GetElem(LinkList L, int i) {
int j = 1;
LNode* p = L->next;//让p指向第一个有数据的结点
if (i == 0) {
return L;
}
if (i<0)
{
return NULL;
}
while (p && j < i) {
p = p->next;
j++;
}
return p;
}
整体思路类似按序号查找
//按值查找结点
LNode* LocateElem(LinkList L, ElemType e) {
LNode* p = L->next;
while (p && p->data != e) {
p = p->next;
}
return p;
}
关键是找到要插入位置的前一个结点
插入顺序:让新结点的next等于前一个结点的next
前一个结点的next等于新结点
下面例子:
构建链表(3 4 5 6 7 8),测试往第二个位置插入99,插入成功打印链表
//往第i个位置插入元素
bool ListFrontInsert(LinkList& L, int i, ElemType e) {
LNode * p = GetElem(L, i - 1);//查找到插入位置的前一个元素结点
if (NULL == p) {
return false;//插入的位置不对
}
else
{
LNode* s = (LNode*)malloc(sizeof(LNode));//给新插入的结点申请空间
s->data = e;//要插入的值放入对应空间
s->next = p->next;
p->next = s;
}
return true;
}
关键还是找到要删除点的前一个结点
让前一个结点的next等于要删除结点的next,再释放要删除的结点的内存
例子:
新建链表(3 4 5 6 7 8),在第二个位置插入99,然后再删除第二个位置的值
//删除第i个位置的元素
bool ListDelete(LinkList& L, int i) {
LNode* p = GetElem(L, i - 1);//找到删除元素的前一个元素
if (NULL == p) {
return false;
}
LNode* q = p->next;//获取到要删除的元素
if (NULL == q)
{
return false;//要删除的元素不存在
}
p->next = q->next;
free(q);//释放q的内存
q = NULL;//为了避免野指针,考研不考察是否置为NULL
return true;
}
考研大题还没有出过双向链表的大题
主要掌握增删查
typedef int ElemType;
typedef struct DNode {
ElemType data;
DNode* prior;//前驱
DNode* next;//后继
}DNode,*DLinkList;
//头插法
DLinkList Dlist_head_insert(DLinkList& DL) {
DL = (DLinkList)malloc(sizeof(DNode));
DNode* s;
int x;
DL->prior = NULL;
DL->next = NULL;
cin >> x;
while (x != 9999) {
s = (DNode*)malloc(sizeof(DNode));
s->data = x;
s->next = DL->next;
if (DL->next != NULL) {//对于最开始插入第一个结点的时候,不执行这个操作
DL->next->prior = s;
}
s->prior = DL;
DL->next = s;
cin >> x;
}
return DL;
}
查看内存情况
//打印
void PrintDList(DLinkList DL) {
DL = DL->next;
while (DL)
{
cout << DL->data << " ";
DL = DL->next;
}
cout << endl;
}
//尾插法
DLinkList Dlist_tail_insert(DLinkList& DL) {
DL = (DLinkList)malloc(sizeof(DNode));
DNode* s;
DNode* r;//尾指针
r = DL;
int x;
cin >> x;
while (x != 9999) {
s = (DNode*)malloc(sizeof(DNode));
s->data = x;
r->next = s;
s->prior = r;
r = s;
cin >> x;
}
r->next = NULL;
return DL;
}
//获取元素
DNode* GetElem(DLinkList DL, int i) {
int j = 1;
DNode* p = DL->next;
if (i==0)
{
return DL;
}
if (i<0)
{
return NULL;
}
while (p&&j<i)
{
p = p->next;
j++;
}
return p;
}
演示在第二个位置插入66
//中间插入
bool DListFrontInsert(DLinkList& DL, int i, ElemType e) {
DNode* p = GetElem(DL, i - 1);
if (NULL==p)
{
return false;
}
DNode* s = (DNode*)malloc(sizeof(DNode));
s->data = e;
s->next = p->next;
p->next->prior = s;
p->next = s;
s->prior = p;
return true;
}
这里模拟删除第二个结点
//删除第i个结点
bool DListDelete(DLinkList& DL, int i) {
DNode* p = GetElem(DL, i - 1);//获得前一个结点
if (NULL == p) {
return false;
}
DNode* q = p->next;//q为要删除的结点
if (NULL == q)//要删除的元素不存在
{
return false;
}
p->next = q->next;
if (p->next!=NULL)
{
q->next->prior = p;
}
free(q);
return true;
}
循环链表了解原理即可。实现的时候就是把最后一个结点的next指向头指针L
了解原理即可。循环双链表实现起来将最后一个结点的next指向头指针,头指针的prior指向最后一个结点
考研还没有考过,现实生活中使用较少
源码下载