链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。可以理解为一种更加好用的数组,其可以避免或者减少存放位置用完的情况。
那么可以用以下代码进行一个简单的单链表的创建
#include<stdio.h>
#include<stdlib.h>
这里使用两个头文件,其中的#include<stdlib.h> 这里提供malloc,用于为整个链表的每一个节点分配内存。
然后便是最重要的单链表的节点的创建的节点结构体
我在这里面使用两个部分,一个是数据域,另一个是使用的指针域同时为了后期更好的使用这个结构体,为这个结构体重新命名,得到以下的代码
typedef struct node{
int data;
struct node* next;
}Node;
因为是简单的单链表,所以我并不需要使用过多的函数,其中最主要的只有三个,一个是用于创建一个新的节点的函数,一个是对传入 数据进行赋值的函数,最后一个是对复制输出的函数,当然这个函数可以和第二个合并,因为这只是一个简单的单链表,所以功能并不需要特别复杂,只需要最简单的数据进行输入和输出即可
那么在这三个部分会分开说一下
①用于创建一个新的节点的函数
这里重点是要对内存进行分配而且对于内存中的每一个已经分配好的部分进行处理,避免野指针的出现
Node* wu(){
Node* node = (Node*)malloc(sizeof(Node)); //申请一个新的节点空间
node->next = NULL; //对这个新的节点中的next指针进行初始化
return node; //将这个空间的指针返回
}
② 传入 数据进行赋值的函数
那么这里要注意要输入自己所需要的结点的数量,毕竟不太可能会有提前准备很多的一份一份内存准备让你使用的情况。那么输入之后便是创建头节点,在循环中创建每一个节点
那么事情便很明确了,只需要在这里创建一个节点,同时对中间进行赋值即可
void init(Node* head){
int len; //输入要录入节点的个数
printf("请输入您要存入节点的个数:");
scanf("%d", &len);
Node* now_node = head; //now_node变量为Node指针变量,用来指向当前节点
for(int i = 0; i < len; i++){
Node* new_node = wu(); //new_node变量为Node指针变量,用来存入新的数值
scanf("%d", &(new_node->data));
now_node->next = new_node; //将new_node变量放入链表最后面
now_node = now_node->next; //now_node变量移动到最后一个节点,以方便接下来新节点的加入
}
}
用now_node和new_node进行对这个部分区分。这里的for循环当中的scanf中的new_node->data;这个部分是利用了在上面数据域,从而实现了对里面的赋值值得注意的是对其中的赋值只能是数字等,如果将%d换成%c便可以对其中的单个字符进行输入
③对复制输出的函数
对于已经复制过来的链表,如果不对他进行查找,那么便很难找到正确的哪个部分,所以可以对其进行便利的输出,只需要找到头结点就好了,具体代码如下
void print(Node* head){
Node* now_node = head->next;
while(now_node != NULL){
printf("%d ", now_node->data);
now_node = now_node->next;//插入节点,
}
printf("\n");
}
在这里有两种办法输出,第一种是在输入的地方将i进行一个局部变量转化成为全局变量的方法。第二种是用while循环,知道最后一个节点指向的是空为止,那么便很好理解。这里可以用以上的方法进行输出,对于now_node。
最后将这三个函数放到一起同时注意free掉使用的空间即可。
#include<stdio.h>
#include<stdlib.h>
typedef struct node{
int data;
struct node* next;
}Node;
//Node* wu(); //创建一个新的节点
//void init(Node* head); //对传入的链表进行赋值
//void print(Node* head); //输出链表
Node* wu(){
Node* node = (Node*)malloc(sizeof(Node)); //申请一个新的节点空间
node->next = NULL; //对这个新的节点中的next指针进行初始化
return node; //将这个空间的指针返回
}
void init(Node* head){
int len; //输入要录入节点的个数
printf("请输入您要存入节点的个数:");
scanf("%d", &len);
Node* now_node = head; //now_node变量为Node指针变量,用来指向当前节点
for(int i = 0; i < len; i++){
Node* new_node = wu(); //new_node变量为Node指针变量,用来存入新的数值
scanf("%d", &(new_node->data));
now_node->next = new_node; //将new_node变量放入链表最后面
now_node = now_node->next; //now_node变量移动到最后一个节点,以方便接下来新节点的加入
}
}
void print(Node* head){
Node* now_node = head->next;
while(now_node != NULL){
printf("%d ", now_node->data);
now_node = now_node->next;//插入节点,
}
printf("\n");
}
int main(){
Node* head = wu();
init(head);
print(head);
}
创造出来了一个简单的单链表,那么便可以试着做一些更有意思的事情,像链表的增删改查,双向,逆置,循环也就都可以实现了
那么便从插入一个新的节点开始吧
插入时我们要考虑到,插入的位置超没超过链表本身的范围,如果有人想不开在最开始的地方插入怎么办。
那么便可以先考虑一些简单的部分比如说,我可以让一个新的节点的下一个节点指向的原本插入位置前一个结点指向的下一个
,至于为什么不进行顺序着连接,主要是为了防止地址的丢失。那么这就简单了,
Node* find_x(Node* head, int x){
Node* now_node = head->next;
while(now_node->data != x && now_node->next != NULL)
now_node = now_node->next;
return now_node;
}
void insert_x(Node* head, int x){
//建立一个新的插入节点
Node* new_node = create_node();
scanf("%d", &new_node->data);
//找到插入位置
//Node* now_node = head->next;
//while(now_node->data != x && now_node->next != NULL) now_node = now_node->next;
Node* now_node = find_x(head, x);
//插入
new_node->next = now_node->next;
now_node->next = new_node;
}
这样便可以做到简单的插入,但还有一个问题,如果我想要在最开始的地方插入一个新的节点作为头节点该怎么办,其实处理方法相差不多
void insertElem(LNode *L, int p, int e)
{
LNode *temp = L;
int i = 0;
while(i<p-1)
{
temp = temp ->next;
++i;
}
LNode *s = (LNode*)malloc(sizeof(LNode)); //创建新结点
s ->data = e;
s->next = temp ->next;
temp ->next = s;
}
删除节点
删除和插入使用的方法相差不大,主要都是对于先判断链表是不是空的,如果是空的就没有必要对这个链表做删除,同时还有是否超过了范围
当我打算删除的地方是0号位时,会不会发生头结点的消失
删除的节点必须要free掉,避免野指针的出现。
那么就是考虑方式,可以将要删除的节点的前后作为重要的位置点,将这两点中靠前的作为a,将a->next=a->next->next,然后再将原先a->next给free掉即可
那么代码如下
Node* find_n(Node* head, int n){
//找到插入位置
Node* now_node = head->next;
while(n-- && now_node->next != NULL)
now_node = now_node->next;
return now_node;
}
void shanchu_n(Node* head,int n){
Node* now_node = find_n(head, n-1);//寻找上一个节点
Node* node = now_node->next;
now_node->next = node->next;//上一个节点指向下一个,跳过这个节点
free(node);//删除
}
在删除了头结点的情况下,只需要将头结点的下一个节点当作头节点即可。
node*a=head;
head=a->next;
free(a);
a=NULL;
查找链表中的元素
这里的查找使用了链表的头节点和指定的数据,同时返回值是该数据的对应节点的排序。
Node* list_find(Node* head,int data)
{
if(head == NULL)
return NULL;
Node* node = head;
while(node)
{
if(node->data == data)
return node;//返回该数据对应的结点
node = node->next;
}
return NULL;//链表中不存在该数据,返回NULL
}
最后是修改链表中节点的值。
其中的参数主要是用到链表的头节点,更改的位置,还有就是新的元素
其返回值为对应的节点,可以在最后进行对整个链表的输出进行判断。
bool modify_data(Node* head, int data, int val)
{
Node* node = list_find(head,data);
if(node)
{
node->data = val;
return true;
}
return false;
}
那么链表的增删改查就结束了
接下来的主要就是双向链表和循环链表
①双向链表最主要的是在开始创建的节点结构体中要做到再定义一个新的指针用以从后向前在进行一次指向前面所有的存在
那么这样来说便简单很多
可以试一下
#include <stdio.h>
3include<stdlib.h>
struct DouLinkNode {
int data ;
struct DouLinkNode *pre ,*next;
};
struct DouLinkNode *create(int n)
{
int x;
struct DouLinkNode *head = (struct DouLinkNode *)malloc(sizeof(struct DouLinkNode));
struct DouLinkNode *p,*s;
p = head;
p->pre = NULL;
while(n) {
s = (struct DouLinkNode*)malloc(sizeof(struct DouLinkNode));
printf("input data of the node:data=");
scanf("%d",&x);
s->data = x;
p->next = s;
s->pre = p;
p = s;
n--;
}
s->next = NULL;
return head;
}
/* 双向链表打印 */
void display(struct DouLinkNode *head)
{
struct DouLinkNode *p = head->next;
while(p->next) {
printf("%d <---> ",p->data);
p = p->next;
}
printf("%d \n",p->data);
}
void insertListHead(struct DouLinkNode *pList, int data)
{
struct DouLinkNode *pNode = (struct DouLinkNode *)malloc(sizeof(struct DouLinkNode));
pNode->data = data;
pNode->next = pList->next;
pNode->pre = pList;
if (NULL != pNode->next) {
pNode->next->pre = pNode;
}
pList->next = pNode;
}
这样便可以认为是已经初步建立好了,而这里面也会涉及到增删改查,需要注意的是,这其中的增删改查要注意这个时候并不是只需要注意一个指针,这里有两个指针
所以以下只用简单的使用代码即可
插入
int a(struct az *pList, int data, int index)
{
struct az *pCur = pList;
int len = get_List_Length(pCur);
if(index > len) {
return -1;
} else if (index == 0) {
insertListHead(pList, data);
return 0;
} else if(index == len) {
insertListTail(pList, data);
return 0;
}
删除
int a(struct az*pList, int key)
{
struct az*pPre = pList;
struct az*pCur = pList->next;
int count = 0;
while(NULL!=pCur) {
if(pCur->data == key) {
if(NULL != pCur->next) {
pCur->next->pre = pCur->pre;
}
pCur->pre->next = pCur->next;
pPre = pCur;
pCur = pCur->next;
free(pPre);
count ++;
} else {
pPre = pCur;
pCur = pCur->next;
}
}
return count;
}
查找
int FindList(Node * head,int elem)
{
Node * temp=head;
int i=1;
while (temp)
{
if (temp->data==elem)
{
return i;
}
i++;
temp=temp->next;
}
return -1;
}
删除
Node * DeleteList(Node * head,int data)
{
Node * temp=head;
while (temp)
{
if (temp->data==data)
{
if(temp->pre == NULL)
{
head=temp->next;
temp->next = NULL;
free(temp);
return head;
}
else if(temp->next == NULL)
{
temp->pre->next=NULL;
free(temp);
return head;
}
else
{
temp->pre->next=temp->next;
temp->next->pre=temp->pre;
free(temp);
return head;
}
}
temp=temp->next;
}
printf("Can not find %d!\r\n",data);
return head;
}
循环链表中重要的是将最后一个元素所指向的NULL改变成为头结点的地址,所以如此看来,循环链表和单链表更加的相像,也会比双向链表更加的方便,高效。