线性表-----链表篇

 目录

前言

顺序表

链表

概念

与数组的不同 

单链表

1. 创建节点

2.插入节点

尾插节点(形成链表结构)

向指定位置插入节点(链表已有)

​编辑

3.遍历链表数据

4.获取链表长度

5.删除节点

删除尾节点

删除指定节点

6.清空链表

7.链表的数据查找

8.链表的转置

 测试集

 环形链表

创建环形链表 

双向循环链表

创建双向循环链表


前言

        在此之前我们学习过了数组,数组也是一种顺序表,连续储存结构,但是数组本质上是一种静态空间,而我们主要讲的是动态空间的顺序表,顾名思义也就是我们常见的链表,链表又分为单链表以及环状链表双向循环链表,下面我们就一起来看看吧!(我已经上传代码,可下载代码资源)

顺序表

定义:

顺序表,全名顺序存储结构,是线性表的一种。通过《什么是线性表》一节的学习我们知道,线性表用于存储逻辑关系为“一对一”的数据,顺序表自然也不例外。
不仅如此,顺序表对数据的物理存储结构也有要求。顺序表存储数据时,会提前申请一整块足够大小的物理空间,然后将数据依次存储起来,存储时做到数据元素之间不留一丝缝隙。 

我们常见的顺序表有数组和链表等,前者是一个静态空间的数据储存结构,后者是一个动态空间数据储存结构。二者都可以去通过引索去实现表的操作,下面就看链表的相关操作。

链表

概念

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作。 

 链表的节点是分为两部分:数据域指针域

数据域:存放各种数据,比如int、char类型等等

指针域:作为指向下一个节点的桥梁 

typedef struct student{
    //数据域
    int index;//作为下标表示第几位
    long num;//学号
    char name[20];//名字
    int score;//分数
    //指针域
    struct student* next;//指向下一位的指针
}Student;

图解

 

与数组的不同 

单链表

  单链表是作为最简单的一种线性表的数据结构,通过动态空间分配可以实现单链表的创建,下面我们就学习单链表的创建以及增删改查等方法。

1. 创建节点

这里我们就不去按照之前学习链表时的做法了,之前我们是一次性去创建节点的同时还把节点连接起来,这里我们就把这两个步骤分开来做,让代码更加灵活。 

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct date{
    long num;//学号
    char name[20];//名字
    int score;//分数
}Date;//存放数据
typedef struct student{
    //数据域
    Data x;
    //指针域
    struct student* next;//指向下一位的指针
}Student;


//创建节点
Student* create_node(Date x) {
    Student* node = (Student*)malloc(sizeof(Student));
    //数据赋值
    node->x = x;
    //指针域指向空
    node->next = NULL;
    //返回这个节点
    return node;
}

2.插入节点

尾插节点(形成链表结构)

//把节点连接起来
void create_studentlist(Student**head,Date x) {
    Student* new_node = create_node(x);
    //如果头指针指向空节点的话,那么将头指针指向这个新的节点
    if (*head == NULL) {
        *head = new_node;
    }
    //如果头指针指向一个不为空的节点的话,那么就要顺着这个链表找到最后的节点,把新节点放入
    else {
        Student* move=*head;//创建一个移动节点,找到最后一个节点的位置
        while (move->next != NULL) {
            move = move->next;  //一直往后移
        }
        move->next = new_node;//把新节点放入进去
    }
}

向指定位置插入节点(链表已有)

//指定位置插入节点
void insert_node(Student** head, Student *new_node,int n) //向链表第n个位置插入节点
{
    Student* move=*head;
    //通过循环找到这个节点
    for (int i = 0; i < n; i++)
    {
        move = move->next;
    }
    //下面这两步的顺序千万不能反过来!!!
    new_node->next = move->next;
    move->next = new_node;
}

3.遍历链表数据

//输出数据
void listprint(Student**head)
{
    Student* cur=*head;
    while (cur != NULL) {
        printf("%ld %s %d\n", cur->x.num, cur->x.name, cur->x.score);
        cur = cur->next;
    }
    printf("output over!");
}

4.获取链表长度

//获取链表长度
int get_listlength(Student**head)
{
    int length=0;
    Student* cur = *head;
    while (cur != NULL)
    {
        length++;
        cur = cur->next;
    }
    return length;//返回长度的大小
}

5.删除节点

删除节点是链表的基本操作之一,分为删除尾节点和删除指定的节点(不是尾节点),这两种情况要去分类讨论,下面请看代码案例:

删除尾节点

//删除尾节点
void pop_lastnode(Student** head)
{
    //如果没有节点,就直接返回
    if (*head == NULL)
        return;
    //如果只有一个节点的话,就直接删除
    else if ((*head)->next == NULL)
    {
        free(*head);
        *head = NULL;//重新置空
    }
    /*如果有多个节点的话,要找到最后一个节点的上一个节点,删除掉最后一个节点
    然后再把这个节点的next指向空*/
    else{
        Student* move = *head;
        while (move->next->next!=NULL)
        {
            move = move->next;
        }
        free(move->next);
        move->next = NULL;
    }
}

删除指定节点

//删除指定节点
void pop_node(Student** head, int n)//删除第n个节点
{
    Student* move = *head;
    Student* p;
    //通过循环依次去找到要删除节点的上一个节点
    for (int i = 0; i < n-1; i++)
    {
        move = move->next;
    }
    p = move->next;//此时要删除的节点标记为p节点
    move->next = p->next;//让p节点的上一个节点指向p节点的下一个节点,此时p节点已经断链
    free(p);
}

6.清空链表

//清空链表,释放全部的空间
void clear_list(Student**head){
    Student* cur=*head;
    Student* next;
    while (cur != NULL) {
        next = cur->next;
        free(cur);
        cur = next;
    }
    *head = NULL;//头指针置空
    printf("clear over!\n");
}

7.链表的数据查找

//数据查找

//按学号查找
Student* list_search_num(Student** head, int num) {
    Student* cur = *head;
    while (cur!=NULL) {
        if (cur->x.num == num) { 
            return cur; //找到了就返回这个节点
        }
        else
            cur = cur->next;
    }
    printf("No find");
    return NULL;
}

//按名字查找
Student* list_search_name(Student** head, char *name) {
    Student* cur = *head;
    while (cur != NULL) {
        if (strcmp(cur->x.name,name)==0) {
            return cur;   //找到了就返回这个节点
        }
        else
            cur = cur->next;
    }
    printf("No find");
    return NULL;
}

8.链表的转置

我们已有的链表顺序是: 1->2->3->4->5  现在我想让这个链表顺序变为5->4->3->2->1(如下图所示)   这个过程就是链表的转置,我们可以去通过头插法去实现

头插法步骤如下: 

//链表的转置(头插法)
void list_reverse(Student** head) {
    Student *L, *start,* p, * q;
    //开辟一个暂存节点L
    L = (Student*)malloc(sizeof(Student));
    //初始化
    start = *head;
    L->next = start;
    p = L->next;
    L->next = NULL;
    while (p!= NULL) {
        q = p->next;
        p->next = L->next;
        L->next = p;
        p = q;
    }
    *head = L->next;//将此时的头结点等于新的头结点
    free(L);//释放掉暂存节点
    printf("reverse over!\n");
}

 测试集

//测试结果
int main()
{
    Date a[4] = { {1,"xiaoming",90} ,{ 2,"Jack",80 } ,{ 3,"Amy",98 } ,{ 4,"John",77 } };

    Student* head = NULL;//实例化一个头节点指针
    for (int i = 0; i < 4; i++) {
        //要把实例化的头指针的地址存入进去
        create_studentlist(&head, a[i]);//依次成链
    }
    listprint(&head);//输出数据
    printf("当前节点数量: %d\n", get_listlength(&head));
    list_reverse(&head);//逆转链表
    listprint(&head);//输出数据
    pop_lastnode(&head);//删除最后一个节点
    printf("当前节点数量: %d\n", get_listlength(&head));
    listprint(&head);//输出数据
    clear_list(&head);//清空链表
    printf("当前节点数量: %d\n", get_listlength(&head));

}
//输出如下:
/*1 xiaoming 90
2 Jack 80
3 Amy 98
4 John 77
output over!
当前节点数量: 4
reverse over!
4 John 77
3 Amy 98
2 Jack 80
1 xiaoming 90
output over!
当前节点数量: 3
4 John 77
3 Amy 98
2 Jack 80
output over!
clear over!
当前节点数量: 0
*/

 环形链表

链表,从名字上来看是一条数据链,一般的链表其末尾节点是没有指向的,但当把链表的末尾节点指定为指向头节点时,则构成了一个环形链表。所以要想去建立一个环形链表只需要把最后一个节点重新指向头节点就行了,或者指向其他节点可以形成局部环形链表。下面我就讲讲通过代码去创建环形链表,相关操作方法(增删改查)跟上面所说的基本一样。

创建环形链表 

#include<stdio.h>
#include<stdlib.h>
typedef struct node {
	int date;
	struct node* next;
}Node;
//创建节点
Node *create_node(int date) {
	Node* node = (Node*)malloc(sizeof(Node));
	node->date = date;
	node->next = NULL;
	return node;
}
//把节点连起来
void create_annuallist(Node** head,int date) {
	Node* new_node = create_node(date);
	if (*head == NULL) {
		*head = new_node;
	}
	else
	{
		Node* cur = *head;
		while (cur->next) {
			cur = cur->next;
		}
		cur->next = new_node;
	}//成链
	//成环
	new_node->next = *head;
}

当你仔细细看的话,前面跟链表的创建基本上没什么区别,实际上就在最后面加上一个尾节点指向头节点就是了!

双向循环链表

带头双向循环链表是链表当中结构最复杂的链式结构,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个链表虽然结构复杂,但是使用代码实现以后会发现能带来很多优势。

创建双向循环链表

实现思路:

要想创建双向循环链表,就意味着在单相链表的基础上增加一个前指针来指向上一个节点,然后在成链的过程中后一个节点要指向前一个节点,前一个节点指向后一个节点,最后头尾节点互相指向就可以形成双向循环了。

代码实现:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//创建双向循环链表
typedef struct node {
	struct node* prev; //前指针
	char date[10];//数据域
	struct node* next; //后指针
}D_node;
D_node* create_D_node(char*date) {
	D_node* new_node = (D_node*)malloc(sizeof(D_node));
	strcpy(new_node->date, date);
	//前后指针都指向空,初始化
	new_node->prev = NULL;
	new_node->next = NULL;
	return new_node;
}
void create_D_list(D_node** head, char* date) {
	D_node* new_node = create_D_node(date);
	if (*head == NULL) {  //判断头结点是否为空
		*head = new_node;
	}
	else
	{
		D_node* cur = *head;
		while (cur->next) {
			cur = cur->next;
		}
		//双向指向(前指向后,后指向前)
		new_node->prev = cur;
		cur->next = new_node;
	}
	//头尾指针互相指向,成环
	new_node->next = *head;
	(*head)->prev = new_node;
}

 好了,以上就是本期的全部内容了,我们下一期再见!

分享一张壁纸:

猜你喜欢

转载自blog.csdn.net/m0_73633088/article/details/132776167