[数据结构]#2 链表

有关顺序表与链表的操作,简而言之就是四个字——增、删、改、查。

在上一篇文章,我们提到顺序表的插入与删除https://blog.csdn.net/Marvinem13/article/details/148900187?fromshare=blogdetail&sharetype=blogdetail&sharerId=148900187&sharerefer=PC&sharesource=Marvinem13&sharefrom=from_linkhttps://blog.csdn.net/Marvinem13/article/details/148900187?fromshare=blogdetail&sharetype=blogdetail&sharerId=148900187&sharerefer=PC&sharesource=Marvinem13&sharefrom=from_link但我们也提到了顺序表的缺点:

插入和删除操作需要移动大量元素;

当线性表长度变化较大时,难以确定存储空间的容量;

造成存储空间的“碎片”;

而为了解决顺序存储的缺点,即插入删除和动态存储的问题,我们将使用更加健壮的链表。

线性表链式存储的结构,特点是一组任意的存储单位存储线性表的数据元素,存储单元可以是连续的,也可以是不连续的,可以被存储在任意内存未被占用的位置上。

所以单就这方面来说,顺序表只需要存储数据元素信息就可以了,在链式结构中还需要一个元素存储下一个元素的地址。

为了表示每个数据元素,a[i]与其后边的数据元素a[i+1]之间的逻辑关系,对a[i]来说,除了存储它自身的信息之外,还需要存一个指示后续的信息。我们把存储元素信息的域叫数据域,把存储后继位置的域叫指针域。而这两部分信息组成数据元素a[i]的存储映像,叫做结点(Node)。

其结构体描述为:

typedef struct person {
	char name[32];
	char sex;
	int age;
	int score;
}DATATYPE;         //简易学生管理系统

typedef struct node {
	DATATYPE data;
	struct node *next;
}LinkNode;

typedef struct list {
	LinkNode *head;
	int tlen;
	int clen;
}LinkList;

链表的操作:

首先是基本框架的搭建:

#include "linklist.h"
#include 
#include 
#include 

//创建链表
LinkList *CreateLinkList()
{
  LinkList *ll = malloc(sizeof(LinkList));    //申请堆空间
  if (NULL == ll)
  {
    fprintf(stderr, "CreateLinkList malloc\n");
    return NULL;
  }

  ll->head = NULL;
  ll->clen = 0;
  return ll;
}

//判断是否为空
int IsEmptyLinkList(LinkList *list)
{
  return 0 == list->clen;
}

//打印链表内容
int ShowLinkList(LinkList *list)
{
  LinkNode *tmp = list->head;
  while (NULL != tmp)
  {
    printf("name:%s age:%d sex:%c score:%d\n", tmp->data.name, tmp->data.age,
           tmp->data.sex, tmp->data.score);
    tmp = tmp->next;  // tmp++;
  }
  return 0;
}

//链表有效元素个数
int GetSizeLinkList(LinkList *list)
{
  return list->clen;
}

//释放内存
int DestroyLinkList(LinkList **list)
{
  int size = GetSizeLinkList(*list);
  int i = 0;
  for (i = 0; i < size; i++)
  {
    LinkNode *tmp = (*list)->head;
    (*list)->head = (*list)->head->next;
    free(tmp);
  }
  free(*list);
  *list = NULL;
  return 0;
}

接着是线性表链式结构的重要操作——增删改查:

1.增

//头插
int InsertHeadLinkList(LinkList *list, DATATYPE *data)  // O(1)
{
  //新结点的初始化
  LinkNode *newnode = malloc(sizeof(LinkNode));
  if (NULL == newnode)
  {
    fprintf(stderr, "InsertHeadLinkList malloc\n");
    return 1;
  }
  memcpy(&newnode->data, data, sizeof(DATATYPE));
  newnode->next = NULL;

  // 把新节点连接到链表中
  if (IsEmptyLinkList(list))  // empty
  {
    list->head = newnode;
  }
  else
  {
    newnode->next = list->head;
    list->head = newnode;
  }

  list->clen++;
  return 0;
}

//尾插
int InsertTailLinkList(LinkList *list, DATATYPE *data)  // O(n)
{
  LinkNode *tmp = list->head;

  if (IsEmptyLinkList(list))
  {
    return InsertHeadLinkList(list, data);
  }
  else
  {
    int size = GetSizeLinkList(list);
    int i = 0;
    for (i = 0; i < size - 1; i++)
    {
      tmp = tmp->next;
    }

    LinkNode *newnode = malloc(sizeof(LinkNode));
    if (NULL == newnode)
    {
      fprintf(stderr, "InsertTailLinkList malloc\n");
      return 1;
    }

    memcpy(&newnode->data, data, sizeof(DATATYPE));
    newnode->next = NULL;
    tmp->next = newnode;
    list->clen++;
  }
  return 0;
}


2.删

//删除
int DeleteLinkList(LinkList *list, char *name)
{
  LinkNode *prev = list->head;
  LinkNode *tmp = list->head;

  if (IsEmptyLinkList(list))
  {
    return 1;
  }
  // 是不是需要删除第一个节点
  if (0 == strcmp(tmp->data.name, name))
  {
    list->head = tmp->next;
    free(tmp);
    list->clen--;
    return 0;
  }
  // 中间节点或最后的节点
  while (prev->next)
  {
    if (0 == strcmp(prev->next->data.name, name))
    {
      tmp = prev->next;
      prev->next = tmp->next;
      free(tmp);
      list->clen--;
      return 0;
    }
    prev = prev->next;
  }
  return 0;
}

3.改

//修改链表
// int ModifyLinkList(LinkList *list, char *name, DATATYPE *data)
int ModifyLinkList(LinkList *list, PFUN fun, void *arg, DATATYPE *data)
{
  LinkNode *tmp = FindLinkList(list, fun, arg);
  if (NULL == tmp)
  {
    printf("modify failure...\n");
    return 1;
  }
  memcpy(&tmp->data, data, sizeof(DATATYPE));
  return 0;
}

4.查

/**
 * @brief 按照指定规则查找 元素
 *
 * @param list  指向单向链表的指针
 * @param fun   函数指针,规定 按照对应规则查找 ,有函数调用者指定
 * @param arg   函数指针的参数
 * @return LinkNode*
 */
// LinkNode *FindLinkList(LinkList *list, char *name)
LinkNode *FindLinkList(LinkList *list, PFUN fun, void *arg)
{
  if (IsEmptyLinkList(list))
  {
    return NULL;
  }
  LinkNode *tmp = list->head;
  while (tmp)
  {
    // if (0 == strcmp(tmp->data.name, name)) // int fun(data,args)
    if (fun(&tmp->data, arg))
    {
      return tmp;
    }
    tmp = tmp->next;  // i++
  }
  return NULL;
}

我们简要总结一下顺序表和链表的优缺点:

1.存储方式

顺序表是一段连续的存储单元,而链表是逻辑结构连续但物理结构(在内存中的表现形式)不连续

2.时间性能(时间复杂度)

查找:顺序表——O(1)   链表——O(n)

插入和删除:顺序表——O(n)    链表——O(1)

3.空间性能

顺序表需要预先分配一定的空间,且大小固定;而链表更加灵活,不需要预先分配,大小可变,是动态分配的

你可能感兴趣的:([数据结构]#2 链表)