day04(数据结构)链表

 链表 Linklist

链表又称单链表、链式存储结构,用于存储逻辑关系为“一对一”的数据。

和顺序表不同同,使用链表存储数据,不强制要求数据在内存中集中存储,各个元素可以分散存储在内存中。

所以在链表中,每个数据元素可以配有一个指针用于找到下一个元素即节点,这意味着,链表上的每个“元素”都长下图这个样子:

1. 链表的特性

逻辑结构:线性结构

存储结构:链式存储

特点:内存不连续,需要通过指针链接,大小不固定。解决顺序表插入删除麻烦的问题。

操作:增删改查

struct node
{
    int data;   //数据域:存储数据
    struct node *next;    //指针域:存储下一个节点的地址
};

2. 单向链表

有头链表:存在一个头节点,头节点数据域无效,指针域有效。

无头链表:每一个节点的数据域和指针域都有效。

遍历无头单向链表

#include <stdio.h>
#include <stdlib.h>
typedef char datatype;
typedef struct node
{
    datatype data;     //数据域用来存数据
    struct node *next; //指针域用来存下一个节点的地址
} node_t, *node_p;

// node_t A; //等同于 struct node A;
// node_p p; //等同于 struct node *p;

int main(int argc, char const *argv[])
{
    //1.定义4个节点
    node_t A = {'a', NULL};
    node_t B = {'b', NULL};
    node_t C = {'c', NULL};
    node_t D = {'d', NULL};

    //2.连接四个节点
    A.next = &B; //连接A和B
    B.next = &C;
    C.next = &D;

    //3.定义一个头指针指向第一个节点
    node_p p = &A;

    //4. 通过指针遍历无头单向链表
    while (p != NULL)
    {
        printf("%c ", p->data); //打印节点中的数据域 a b c d
        p = p->next;            //让p指向下一个节点
    }
    printf("\n");

    return 0;
}


遍历有头单向链表

#include <stdio.h>
#include <stdlib.h>
typedef char datatype;
typedef struct node
{
    datatype data;     //数据域用来存数据
    struct node *next; //指针域用来存下一个节点的地址
} node_t, *node_p;

// node_t A; //等同于 struct node A;
// node_p p; //等同于 struct node *p;

int main(int argc, char const *argv[])
{
    //1.定义4个节点
    node_t A = {'a', NULL};
    node_t B = {'b', NULL};
    node_t C = {'c', NULL};
    node_t D = {'d', NULL};

    //2.连接四个节点
    A.next = &B; //连接A和B
    B.next = &C;
    C.next = &D;

    //3. 定义头节点,数据域无效,指针域有效
    node_t H = {'\0', &A};

    //4. 定义一个头指针,指向头结点
    node_p p = &H;

#if 0
    p = p->next; //先跨越头节点,指向第一个数据域有效的节点A
    while (p != NULL)    //相当于遍历无头链表
    {
        printf("%c ", p->data);
        p = p->next;
    }
    printf("\n");
#else
    while (p->next != NULL)
    {
        p = p->next;
        printf("%c ", p->data);
    }
    printf("\n");
#endif

    return 0;
}

链表尾插法练习

写一个有头单向链表,用于保存输入的学生成绩,实现一输入学生成绩就创建一个新的节点,将成绩保存起来。再将该节点链接到链表的尾,直到输入-1结束。

要求:每个链表的节点由动态内存分配得到 , 也就是用malloc。

过程:

  1. malloc申请空间link_node_t大小作为头节点
  2. 将新节点放到链表尾部

#include <stdio.h>
#include <stdlib.h>
typedef int datatype;
typedef struct node
{
    datatype data;
    struct node *next;
} node_t, *node_p;

int main(int argc, char const *argv[])
{
    node_p pnew = NULL;
    node_p ptail = NULL;
    int score = -1;

    //1. 创建一个头结点,用头指针p指向头节点
    node_p p = (node_p)malloc(sizeof(node_t));
    if (NULL == p)
    {
        perror("p malloc err");
        return -1;
    }
    p->next = NULL; //初始化头节点
    ptail = p;      //让尾指针一开始指向头节点

    //2. 循环输入学生成绩直到-1结束,创建新节点保存学生成绩,尾插到链表。
    while (1)
    {
        scanf("%d", &score);
        if (score == -1)
            break;

        //(1) 创建新节点用来保存学生成绩
        pnew = (node_p)malloc(sizeof(node_t));
        if (NULL == pnew)
        {
            perror("pnew malloc err");
            return -1;
        }
        //(2) 初始化新节点,数据域保存学生成绩,指针域置空
        pnew->data = score;
        pnew->next = NULL;
        //(3) 将新节点连接到链表尾部,也就是ptail所指节点的指针域等于新节点地址
        ptail->next = pnew;
        //(4) 将尾指针移动到新节点,因为尾指针ptail要一直指向最后一个节点
        ptail = pnew;
    }

    //3. 遍历有头链表
    while (p->next != NULL)
    {
        p = p->next;
        printf("%d ", p->data);
    }
    printf("\n");

    return 0;
}

有头单向链表的函数操作
插入指定位置节点:

删除指定位置节点:

删除指定数据节点:

链表转置:

解题思想:

  1. 将头节点与当前链表断开,断开前保存下头节点的下一个节点,保证后面链表能找得到,定义一个q保存头节点的下一个节点,断开后前面相当于一个空的链表,后面是一个无头的单向链表。
  2. 遍历无头链表的所有节点,将每一个节点当做新节点插入空链表头节点的下一个节点(头插),插入之前先用t保存一下q的下一个节点以防插入完找不到原来链表。

#include <stdio.h>
#include <stdlib.h>
typedef int datatype;
typedef struct node
{
    datatype data;
    struct node *next;
} node_t, *node_p;

//创建一个空的有头单向链表
node_p createEmptyLinkList() //create创建 Empty空的 LinkList单向链表
{
    //开辟链表节点大小空间
    node_p p = (node_p)malloc(sizeof(node_t));
    if (NULL == p)
    {
        perror("createEmptyLinkList");
        return NULL;
    }
    //初始化头节点
    p->next = NULL;

    return p;
}

//计算链表的长度。
int lengthLinkList(node_p p)
{
    int len = 0;
    while (p->next != NULL)
    {
        p = p->next;
        len++;
    }
    return len;
}

//向单向链表的指定位置插入数据
//p保存链表的头指针 post 插入的位置 data插入的数据
int insertIntoPostLinkList(node_p p, int post, datatype data)
{
    //(1)容错判断
    if (post < 0 || post > lengthLinkList(p))
    {
        printf("insertIntoPostLinkList err\n");
        return -1;
    }

    node_p pnew = NULL; //记录新节点
    //(2)将指针 遍历到插入位置的前一个节点
    for (int i = 0; i < post; i++)
        p = p->next;
    //(3)malloc创建一个新节点, 初始化新节点
    pnew = (node_p)malloc(sizeof(node_t));
    if (NULL == pnew)
    {
        perror("insertIntoPostLinkList err");
        return -1;
    }
    pnew->data = data;
    pnew->next = NULL;
    //(4)连接新节点到链表, 先连后面,再连前面。(不然前面断了后面就找不到了)
    pnew->next = p->next;
    p->next = pnew;

    return 0;
}

//遍历单向链表
void showLinkList(node_p p)
{
    while (p->next != NULL)
    {
        p = p->next;
        printf("%d ", p->data);
    }
    printf("\n");
}

//判空,为空返回1,不为空返回0
int isEmptyLinkList(node_p p)
{
    return p->next == NULL;
}

//删除单向链表中指定位置的数据 post 代表的是删除的位置
int deletePostLinkList(node_p p, int post)
{
    node_p pdel = NULL;
    //  (1)容错判断:判空||post<0 || post >=长度
    if (isEmptyLinkList(p) || post < 0 || post >= lengthLinkList(p))
    {
        printf("deletePostLinkList err\n");
        return -1;
    }
    // (2) 将指针遍历到删除位置的前一个节点
    for (int i = 0; i < post; i++)
        p = p->next;
    // (3)设指针pdel记录要删除节点:pdel = p->next;
    pdel = p->next;
    // (3)跨过要删除节点:p->next = pdel->next; (或者p->next = p->next->next;)
    p->next = pdel->next;
    // (4)释放要删除节点: free(pdel); pdel=NULL;
    free(pdel);
    pdel = NULL;
    return 0;
}

//思想:
// 循环进行删除,每次删除的头节点的下一个节点:
// (1)定义一个pdel,指向被删除节点
// (2)跨过被删除节点
// (3)释放被删除节点
void clearLinkList(node_p p)
{
    node_p pdel = NULL;
    while (p->next != NULL)
    {
        pdel = p->next;
        p->next = pdel->next;
        free(pdel);
        pdel = NULL;
    }
}

//修改链表中指定的数据
int changePostLinkList(node_p p, int post, datatype data)
{
    //  (1)容错判断:判空||post<0 || post >=长度
    if (isEmptyLinkList(p) || post < 0 || post >= lengthLinkList(p))
    {
        printf("changePostLinkList err\n");
        return -1;
    }
    // (2) 将指针移动到 要修改节点
    for (int i = 0; i <= post; i++)
        p = p->next;
    //(3) 修改数据
    p->data = data;
    return 0;
}

//查找指定数据出现的位置 data被查找的数据 //search 查找
int searchDataLinkList(node_p p, datatype data)
{
    int post = 0; //记录查找节点的位置
    while (p->next != NULL)
    {
        p = p->next;
        if (p->data == data)
            return post;
        post++;
    }
    return -1; //说明数据不存在
}

//删除单向链表中出现的指定数据,data代表将单向链表中出现的所有data数据删除
//思想:p始终指向被删除节点的前一个,让q相当于遍历无头节点,q用于指向删除节点。
int deleteDataLinkList(node_p p, datatype data)
{
    node_p q = p->next; //跳过头节点, 用于遍历无头单向链表
    while (q != NULL)   //相当于遍历无头单向链表
    {
        if (q->data == data) //判断是否是要删除的节点
        {
            //删除:
            //(1)跨过要删除节点
            p->next = q->next;
            //(2)释放删除节点
            free(q);
            q = NULL;
            //让q继续指向p的下一个节点,以便 继续向后遍历
            q = p->next;
        }
        else
        {
            //继续向后遍历,让q和p指针都向后走一个
            q = q->next;
            p = p->next;
        }
    }
}

//链表转置:利用头插实现
void reverseLinkList(node_p p) //reverse反向
{
    node_p t = NULL;    //用来临时保存q的下一个节点,以防头插以后找不到原来链表。
    node_p q = p->next; //断头前定义指针q保存头节点的下一个,用于遍历无头链表
    p->next = NULL;     //断开头节点
    while (q != NULL)   //用q遍历无头链表,遍历一个节点头插一个
    {
        //头插q之前让t指针保存q的下一个,以防下次循环找不到链表
        t = q->next;
        //将q所指节点头插:先连后面再连前面
        q->next = p->next;
        p->next = q;
        //让q找t继续向后遍历进行头插
        q = t;
    }
}

int main(int argc, char const *argv[])
{
    node_p p = createEmptyLinkList();

    for (int i = 0; i < 5; i++)
        insertIntoPostLinkList(p, i, i);
    showLinkList(p); //0 1 2 3 4

    insertIntoPostLinkList(p, -1, 10); //insertIntoPostLinkList err 容错判断打印结果

    deletePostLinkList(p, 1);
    showLinkList(p); //0 2 3 4

    changePostLinkList(p, 1, 1000);
    showLinkList(p); //0 1000 3 4
    insertIntoPostLinkList(p, 4, 1000);
    showLinkList(p); //0 1000 3 4 1000
    deleteDataLinkList(p, 1000);
    showLinkList(p);    //0 3 4
    reverseLinkList(p); //4 3 0
    showLinkList(p);

    clearLinkList(p);
    printf("%d\n", isEmptyLinkList(p)); //1

    return 0;
}

练习:

向一个单链表linklist中的节点t后面插入一个节点p,下列操作正确的是( )

a)p->next = t->next;t->next = p;

b)t->next = p->next;t->next = p;

c)t->next = p;p->next = t->next;

d)t->next = p;t->next = p->next;

总结:顺序表和单向链表比较 (面试)

  1. 顺序表在内存当中连续存储的,通过数组实现;但是链表在内存当中是不连续存储的,通过指针将数据链接在一起。
  2. 顺序表的长度是固定的;链表长度不固定。
  3. 顺序表查找和修改可以根据下标找到对应元素效率对比链表高,但是插入和删除涉及到元素移动效率低;链表,插入和删除方便,查和改效率低。

3. 单向循环链表

约瑟夫环问题,是一个经典的循环链表问题,题意是:已知 n 个人(分别用编号 1,2,3,…,n 表示)围坐在一张圆桌周围,从编号为 k 的人开始顺时针报数,数到 m 的那个人出列;他的下一个人又从 1 开始,还是顺时针开始报数,数到 m 的那个人又出列;依次重复下去,直到圆桌上剩余一个人。

用解决约瑟夫环问题进行杀猴子:

思想:用头指针移动到要杀的猴子的前一个,然后跨过指向猴子的节点。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

typedef struct node_t
{
	int data;
	struct node_t *next;
}link_node_t,*link_node_p;

int main(int argc, const char *argv[])
{
	int i;
	link_node_p pdel = NULL;//用于指向被删除节点
	link_node_p ptail = NULL;//永远指向当前链表的尾 
	link_node_p pnew = NULL;//永远指向新创建的节点
	link_node_p h = NULL;
	int all_num = 7;//猴子总数 
	int start_num = 2; //从几号猴子开始数
	int kill_num = 3;//数到几杀死猴
	printf("请您入猴子总数 起始号码 数到几杀死:\n");
	scanf("%d%d%d",&all_num,&start_num,&kill_num);
	//1.创建出一个单向循环链表
	//(1)创建有all_num个节点的单向链表
	h = (link_node_p)malloc(sizeof(link_node_t));
	if(NULL == h)
	{
		perror("malloc failed");
		return -1;
	}
	h->data = 1;
	h->next = NULL;
	ptail = h;//尾指针指向当前的第一个节点
	for(i = 2; i <= all_num; i++)
	{
		//创建新的节点
		pnew = (link_node_p)malloc(sizeof(link_node_t));
		if(NULL == pnew)
		{
			perror("malloc failed");
			return -1;
		}
		//将新节点装上数据
		pnew->data = i;
		pnew->next = NULL;
		//将新节点链接到链表尾 
		ptail->next = pnew;//链接到链表的尾
		ptail = pnew;//尾指针继续指向当前链表的尾 
	}
	//(2)将头指针保存到链表的尾形成单向循环链表
	ptail->next = h;//形成单向循环链表 
#if 0 //用于调试程序
	while(1)
	{
		printf("%d\n",h->data);
		h = h->next;
		sleep(1);
	}
#endif
	//2.开始杀猴子 
	//(1)将头指针移动到开始猴子的号码处 
	for(i = 1; i < start_num; i++)
		h = h->next;
        printf("start :%d\n",h->data);
	//(2)循环进行杀猴子
	while(h != h->next)//条件不成的时候,就剩一个猴子,只有一个节点
	{
		//将头指针移动到即将删除节点的前一个节点
		for(i = 1; i < kill_num-1; i++)
			h = h->next;

		pdel = h->next;
		//跨过删除节点
		h->next = pdel->next;
		printf("kill is -------------%d\n",pdel->data);
		free(pdel);
		pdel = NULL;
		//杀死猴子猴,从下一个节点开始继续开始数,将头指针移动到开始数的地方
		h = h->next;
	}
	printf("king is=================== %d\n",h->data);
	return 0;
}	

  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值