这里我们来看看链表实现。
typedef int ElementType; typedef struct LNode { ElementType data; struct LNode* next; }LNode,*LinkedList;
首先是这个结构体,和顺序表类似。ElementType是之前的宏定义,你想要它是什么类型他就是什么类型。之后就是这个Lnode指针。
或许你会好奇,欸这里为什么又给了一个指针,还是struct类型的,我们得回到链表的结构上面来讲。
链表一个节点分为两个数据域,不像顺序表,因此它的数据存储密度比起顺序表更低。你的一个节点存放了一个数据,然后,下一个数据域就是存放的地址,存放的下一个链表结点的地址,这个一定要主语,这个是链表的结构。
这里给了两个typedef后的名字,一个是Lnode,一个是*LinkedList。注意,第一个只是结构体名称,后面使用这个的时候用的是" . ",第二个是结构体指针,使用结构体指针访问结构体内容时使用的是"->"。
然后来看头插法建立链表。
我们这里先声明了一个结构体指针 s。
先来一手malloc 分配Lnode大小的空间,返回的指针强转为LinkedList指针类型,才可以赋值给L。
这个节点作为我们的头结点,如果不带头结点实现起来会稍显麻烦,我们后面再来看。头节点最开始是没有下一个结点的,所以头节点的next置NULL。
之后就是常规输入,我们又可以看到,又是一手malloc,不过这里是给了s,这个就是即将插入的新节点。这就是最开始初始化的s,其实这个LNode换成LinkedList也没什么问题。
申请到的空间给了s后,就把我们输入的值赋给s的data域。
然后就是最关键的操作了:
s->next = L->next;
把L的next域的值赋给s的next域,所以这里s就存了头节点原本的下一位的地址。
L->next = s;
然后才是s的地址赋给L的next域。这样就完成了头插操作。这里别搞反了。
//头插法新建链表 LinkedList CreateList1(LinkedList& L) {` LNode* s; int x; L = (LinkedList)malloc(sizeof(LNode));//带头结点的链表,初始化头结点。 L->next = NULL;//最开始的头结点没有下一个节点,置NULL。 scanf_s("%d", &x); while (x!=9999){ s = (LNode*)malloc(sizeof(LNode)); s->data = x;//把读取到的值,给新空间中的data成员。 s->next = L->next; L->next = s; scanf_s("%d", &x); } return L; }
接下来:打印链表,这个比较简单。
//打印链表 void PrintList(LinkedList L) { L = L->next;//第一步跳出头指针。头指针是空的! while (L != NULL) { printf("%3d", L->data);//打印当前数据。 L = L->next;//指向下一个节点 } }
看看注释其实大概就差不多了,要注意的就是第一步必须跳出L头节点。
后面就是L=L->next指向下一个节点,然后依次打印。
之后就是尾插法,这个要注意,有一点点难。
//尾插法建立新链表 LinkedList CreateList2(LinkedList& L) { int x=0; LNode* s, * r = L;//LinkedList r ,s =L 结构体指针 r指向表尾节点. scanf("%d", &x); while (x != 9999) { s = (LNode*)malloc(sizeof(LNode));//即将添加进来的新节点。 s->data = x; r->next = s; r = s; scanf("%d", &x); } r->next = NULL;//尾结点的next指针赋值为null。 return L; }
先设置s指针,供后续新节点的设置。后设置r指针,指向头节点。
之后就是基操,输入操作,新节点s的内存申请与赋值。
注意这个,
r->next = s;//尾部节点指向新节点
这个时候r指向的其实是目前的尾部节点,它就可以直接用->运算符来直接操作当前的节点,当这一步完成了,他会直接:
r = s;
这个时候r指针就直接指向了新插入的节点,这样就保证了r指针始终指向最后一个节点。
r->next = NULL;
还有一步别忘了,r目前所指的节点的next域置为NULL,为后续插入做好准备。
接下来就是查找指定位置的元素。
//查找链表指定i位置的元素 LinkedList GetElem(LinkedList L, int i) { int j = 1; LinkedList p = L->next;//跳出头结点 if (i == 0) { return L;//返回头节点 } if (i < 1) { return NULL;//返回NULL。 } while (p && j < i) { p = p->next;//p指向下一个节点。 j++; } return p; }
其实就是遍历加判断,用p=p->next来遍历,如果符合条件(p不为空&&j小于i),一位一位的往下找,最后return p即可。
按值查询:
//按值查询 LinkedList LocateElem(LinkedList L, ElementType e) { LinkedList p = L->next; while (p != NULL&&p->data!=e) { p = p->next; } return p; }
p!=null && p->data符合条件,就往下遍历,直到合适为止。
在第i个位置插入节点。
//在第i个位置插入节点 bool ListFrontInsert(LinkedList L, int i, ElementType e) { LinkedList p = GetElem(L, i - 1);//拿到要插入的位置的前一个节点的地址。 if (p == NULL) { return false; } LinkedList s = (LNode*)malloc(sizeof(LNode));//新节点建立。 s->data = e; s->next = p->next; p->next = s; return true; }
参数列表里面传入的e就是我们想要插入的值,我们第一步是要拿到要插入的前一个结点的地址,如果这个是空的,那就别插了,说明他是第一个节点,没得搞,返回false。
然后就是基操,给s创建内存,把e赋值给它,
s->next = p->next:把要插入节点位置的前一个位置的next域所存的地址直接给s的next域,这个是不是和头插很相似?这就是头插为什么有个头结点会方便很多,这样插入操作就大大简化了。
p->next = s:要插入位置的前一个结点的next域存放s的地址。
至此,插入操作完成。
最后就是删除节点。
//删除节点 bool ListDelete(LinkedList L, int i) { LinkedList p = GetElem(L, i - 1);//查找删除元素的前驱结点。 if (p == NULL) { return false;//要删除的位置不存在。 } LinkedList q = p->next; p->next = q->next; free(q);//释放空间。 q = NULL; return true; }
这里也是,先找到要删除元素前一个位置的节点,这样才好操作。如果p都没有,那就没得删了,返回false。
用一个q指针来存放p的下一个节点地址。其实就是你要删的节点。
然后把q->next赋值给p->next,这一步其实就是把q的下一个节点的地址直接给了p了,q就被没有直接引用了。
下一步就是用free函数把q毙了,删除操作结束。
避免野指针,可以把q置NULL。