C语言单向链表

在这里插入图片描述

1. 链表价值

链表实现了内存零碎数据的有效组织。比如,当用malloc来进行申请内存时,当内存足够,但由于碎片过多,没有连续内存,只能失败,此时就要用链表来组织数据解决了

2. 静态链表

静态链表不可以动态扩容

2.1. 链表节点定义

typedef struct node
{
  int data; // 数据域,要存储的数据
  struct node *next; // 指针域,指针指向下一个节点,下一个节点的类型是什么样子的,就是本类型,指向就是存储了谁的地址
} Node;
void traverseList(Node *head)
{
  // 遍历逻辑
  // 当前指针:指向第一个元素,并且不为空
  // 打印第一个元素的值,同时能得到第二个元素的地址
  // 第二个元素的地址,赋值给当前指针
  while (head)
  {
    printf("%d", head->data);
    head = head->next;
  }
}
int main() 
{	
	Node a,b,c,d,e,f; // 此节点均在栈上
	Node *head;
	head = &a;  // 1 开头
	a.data = 1; // 2 内容
	b.data = 2;
	c.data = 3;
	d.data = 4;
	e.data = 5;
	a.next = &b; // 3 链接
	b.next = &c;
	c.next = &d;
	d.next = &e;
	e.next = NULL; // 4 结尾
 
 	traverseList(head);
	return 0;
}

3. 动态链表

静态链表的意义不是很大,主要原因是数据存储在栈上,栈的存储空间有限,不能动态分配。要实现存储自由,要动态的申请堆里的空间。

3.1. 链表名字解释

在这里插入图片描述

3.1.1. 空链表

在这里插入图片描述

3.1.2. 非空链表

在这里插入图片描述

3.2. 创建

3.2.1. 尾插法

尾插法:在尾节点插入,每插入一个节点,即成尾节点
在这里插入图片描述

// 定义节点结构体,包含数据域和指向下一个节点的指针
typedef struct node
{
  int data;
  struct node *next;
} Node, *NodePtr; // NodePtr 是 Node 指针类型的别名。
// 遍历链表并打印每个节点的数据
void traverseList(Node *head)
{
  head = head->next; // 跳过头结点,指向第一个有效元素
  while (head)
  {
    printf("%d\n", head->data);
    head = head->next; // 指向下一个节点
  }
}
Node *createList()
{
  Node *head = (Node *)malloc(sizeof(Node));
  if (NULL == head)
    exit(-1);
  head->next = NULL;
  Node *t = head, *cur; // t指针指向头结点,作为尾指针
  int nodeData;
  scanf("%d", &nodeData);
  // 当输入的数据不为0时,继续创建新节点
  while (nodeData)
  {
    // 为新节点分配内存空间
    cur = (Node *)malloc(sizeof(Node)); // 申请空间
    // 如果分配失败,退出程序
    if (NULL == head)
      exit(-1);
    // 将数据放入新节点
    cur->data = nodeData;
    // 尾指针的next指向新节点
    t->next = cur;
    // 尾指针更新为当前新节点
    t = cur;
    scanf("%d", &nodeData);
  }
  // 最后一个节点的next指针指向NULL 
  t->next = NULL;
  return head;
}
int main()
{
  Node *head = createList();
  traverseList(head);
  return 0;
}

3.2.2. 头插法

头插法:在头节点后面插入元素,每插入一个元素,即为首节
在这里插入图片描述

Node *createList()
{
  Node *head = (Node *)malloc(sizeof(Node)); // 创建头结点
  if (NULL == head)
    exit(-1);
  head->next = NULL;      // 初始化头结点的next指针
  Node *cur;              // 当前节点指针
  int nodeData;           // 节点数据
  scanf("%d", &nodeData); // 输入第一个节点数据
  while (nodeData) // 在栈循环的时候,要注意一重循环和二重循环的一致性
  {
    cur = (Node *)malloc(sizeof(Node)); // 为新节点分配内存
    if (NULL == cur)
      exit(-1);
    cur->data = nodeData;   // 将数据放入新节点
    cur->next = head->next; // 新节点的next指针指向当前头结点的next指针
    head->next = cur;       // 头结点的next指针指向新节点
    scanf("%d", &nodeData); // 输入下一个节点数据
  }
  return head; // 返回头结点
}

3.3. 求长度

int lenList(Node *head)
{
  int len = 0;
  head = head->next;
  while (head)
  {
    len++;
    head = head->next;
  }
  return len;
}

3.4. 链表的增删改查

加入头节点的目的,是对于链表的所有操作,不需要更新头指针

3.4.1. 插入操作

在这里插入图片描述
带头节点的链表,插入元素时,即 使新插入的元素成为首元素,不需要更新头节点指针
头插法思想,更适合插入操作,如果采用尾插法思想,则效率较低,比头插法每次都多一次遍历
因此,创建链表的操作可以改写成:创建空链表 * 插入,实际生产中是这样的

// 真正意义上的创建链表,就是创建一个空链表
Node *createList()
{
  Node *head = (Node *)malloc(sizeof(Node));
  if (NULL == head)
    exit(-1);
  head->next = NULL; // 初始化头节点的 next 指针为 NULL
  return head;       // 返回头节点指针
}
// 插入操作,本质就是头插法
void insertList(Node *head, int nodeData)
{
  Node *cur = (Node *)malloc(sizeof(Node)); // 为新节点分配内存
  if (NULL == cur)
    exit(-1);
  cur->data = nodeData;   // 设置新节点的数据
  cur->next = head->next; // 将新节点的 next 指针指向当前头节点的 next 指针
  head->next = cur;       // 将头节点的 next 指针指向新节点
}
int main()
{
  Node *head = createList();
  for (int i = 0; i < 10; i++)
  {
    insertList(head, i);
  }
  traverseList(head);
  return 0;
}

3.4.2. 查找操作

// 查找
Node *searchList(Node *head, int findData)
{
  head = head->next;
  while (head)
  {
    if (head->data == findData)
      break;
    head = head->next;
  }
  return head;
}
int main()
{
  Node *head = createList();
  for (int i = 0; i < 10; i++)
  {
    insertList(head, i);
  }
  traverseList(head);

  printf("len of list = %d\n", lenList(head));

  Node *pfind = searchList(head, 5);
  if (pfind == NULL)
    printf("find failed\n");
  else
    printf("found %d\n", pfind->data);
  return 0;
}

3.4.3. 删除和修改操作

在这里插入图片描述
带头节点的链表删除元素时,既是删除链表的首元素,也是删除头节点的最后一个元素,不需要更新表头指针

void *deleteNodeOfList(Node *head, Node *pfind)
{
  while (head->next != pfind) // head就是pfind的前驱
    head = head->next;
  head->next = pfind->next;
  free(pfind);
  pfind = NULL;
}
int main()
{
  Node *head = createList();
  for (int i = 0; i < 10; i++)
  {
    insertList(head, i);
  }
  traverseList(head);

  printf("len of list = %d\n", lenList(head));

  Node *pfind = searchList(head, 0);
  if (pfind == NULL)
    printf("find failed\n");
  else
  {
  	pfind->data = 100; // 改
    printf("found %d\n", pfind->data);
    deleteNodeOfList(head, pfind);
  }
  traverseList(head);
  return 0;
}

3.5. 排序

3.5.1. 冒泡排序

在这里插入图片描述

#define N 5
int main()
{
  int arr[N] = {5, 4, 3, 2, 1};
  for (int i = 0; i < N - 1; i++)
  {
    for (int j = 0; j < N - i - 1; j++)
    {
      if (arr[j] > arr[j + 1])
      {
        arr[j] ^= arr[j + 1];
        arr[j + 1] ^= arr[j];
        arr[j] ^= arr[j + 1];
      }
    }
  }

  for (int i = 0; i < N; i++)
  {
    printf("%d\n", arr[i]);
  }
  return 0;
}

3.5.2. 换值排序

void popSortList(Node *head)
{
  int len *lenList(head);
  head = head->next;
  Node *p, *q;
  for (int i = 0; i < len - 1; i++)
  {
    p = head;    // 每次内重循环从头开始
    q = p->next; // q总是指向p的下一个节点,也就是被比较的节点
    for (int j = 0; j < len - 1 - i; j++)
    {
      if (p->data > q->data)
      {
        p->data ^= q->data;
        q->data ^= p->data;
        p->data ^= q->data;
      }
      p = p->next;
      q = p->next;
    }
  }
}

3.5.3. 换指针排序

交换节点数据域的方式,当节点数据域很大时,效率低下, 用换指针的方式提高效率

void popSortList(Node *head)
{
  int len = lenList(head);
  Node *prep, *p, *q, *t;
  for (int i = 0; i < len - 1; i++)
  {
    prep = head;
    p = head->next;
    q = p->next;
    for (int j = 0; j < len - 1 - i; j++)
    {
      if (p->data > q->data)
      {
        prep->next = q;
        p->next = q->next;
        q->next = p;
        t = p;
        p = q;
        q = t;
      }
      prep = prep->next;
      p = p->next;
      q = p->next;
    }
  }
}

3.6. 链表反转

在这里插入图片描述

void reverseList(Node *head)
{
  Node *cur = head->next;
  head->next = NULL;
  Node *t;
  while (cur)
  {
    t = cur;
    cur = cur->next;
    t->next = head->next;
    head->next = t;
  }
}

3.7. 链表销毁

// 有多少malloc就应该有多少free
void destroyList(Node *head)
{
  Node *t;
  while (head)
  {
    t = head;
    head = head->next;
    free(t)
  }
}
  • 18
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Qi妙代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值