将两个单链表首尾相连C语言,单链表、双链表、循环链表

这学期的数据结构课有讲到链表,所以再来温故一下,毕竟温故知新嘛。

链表与数组的区别

链表和数组都是线性表,两者区别如下:

数组静态分配内存,链表动态分配内存;更进一步说就是数组不易拓展,但链表易拓展。

数组在内存中连续,链表不连续;更进一步说就是数组可能会造成内存浪费,但链表的内存利用率高;

数组利用下标定位元素,时间复杂度为O(1),链表在最坏情况下是O(n);即在随机性查找上数组比链表更快。

在数组中进行插入和删除操作的时间是O(n),链表是O(1)。

单链表

单链表接触的太多了,单链表中每一个结点由数据域和指针域构成,因为C语言有指针的概念,可以很方便的控制内存,因此大多数关于链表的实现代码都是C/C++版本的,其它语言则大多都是模拟链表,但其实Python也可以实现真实链表,尽管Python不能直接操作内存,但因为Python是动态语言,因此可以直接把对象赋值给新的变量而不需要预先定义。

这篇博客仍以C语言来实现链表,Python版本感兴趣的可以自己尝试。

链表其实就是一个又一个的结点连接而成,这很好理解,如果你的指针学的还不错的话,那么创建一个链表并不是一件难事。

创建结点:

typedef struct Node{

int data;

struct Node *next;

}*Linklist;

创建链表:

Linklist createList(int n)

{

Linklist p,q,head;

head = (Linklist)malloc(sizeof(Linklist));

q = head;

for(int i = 0; i < n; i++)

{

p = (Linklist)malloc(sizeof(Linklist));

scanf("%d",&p->data);

p->next = NULL;

q->next = p;

q = p;

}

return head;

}

输出链表:

void print(Linklist head)

{

Linklist p = head->next;

while(p)

{

printf("%d ",p->data);

p = p->next;

}

}

修改结点:

void changeList(Linklist head,int pos,int x)//修改第pos位的结点,修改值为x

{

Linklist p = head;

int i = 0;

while(i < pos && p != NULL)

{

p = p->next;

i++;

}

if(p)

p->data = x;

else

printf("结点不存在!\n");

}

插入结点:

void insertList(Linklist head,int pos,int x)

{

Linklist p = head;

int i = 0;

while(i < pos - 1 && p != NULL) //遍历到pos位置的前驱结点

{

p = p->next;

i++;

}

Linklist newnode = (Linklist)malloc(sizeof(Linklist));

newnode->data = x;

newnode->next = p->next; //先后插

p->next = newnode; //再前插

}

删除结点(按位置删):

void deleteList(Linklist head,int pos)

{

Linklist p = head; //记录前驱

Linklist q = head->next; //记录当前结点

int i = 0;

while(i < pos - 1 && p != NULL) //遍历到pos位置

{

p = p->next;

q = q->next;

i++;

}

p->next = q->next; //前驱结点指向待删结点的后继结点,即把待删结点隔离出来

free(q); //删除

q->next = NULL; //结点指向空

}

关于单链表的基本操作基本上也就上面那些,其中一些简单的细节被我省略了,比如在插入或者删除之前判断pos是否是不合理的值等,这些都比较好写,也就不再说,另外,因为如果每一个操作都详细辅以文字说明的话篇幅太长,所以大部分情况下我都直接给出了代码并附上少量的关键性注释,目的只是起到一个快速浏览和记忆的作用,因此这篇博客不太适合刚入门的新手,适合学过但记忆的不太牢固的人拿来复习。

双链表

单链表因为每一个结点只保存next的缘故,在它中只能通过一个结点访问它的后继结点而无法访问前驱,如果你想要通过一个结点既能访问前驱又能访问后继,那么可以使用双链表。

双链表与单链表在结构上唯一的不同在于,构成双链表的每一个结点,除了数据域和指向后继的next域之外,新添了一个指向前驱的pre域。在双链表中,查找或者插入等操作不仅可以从链表的头部开始,也可以从尾部开始。仿照创建单链表的思路,在创建双链表时,我们将创建两个哑元结点,分别代表双链表的head和tail。head以及tail结点都不保存数据,head结点的pre域为空,tail结点的next域为空。

双链表看起来是高大上了不少,但对于插入、查找等大部分操作,单链表和双链表在最坏情况下的时间损耗其实是相同的,均为O(n)。双链表更重要的作用是用在散列表中,在采用了双链表的散列表中,删除操作具有O(1)的时间复杂度,因为删除操作不仅需要找到待删结点的前驱还需要找到它的后继,在单链表中只能找到后继,但在双链表中,前驱和后继都可以同时找到。

创建结点:

typedef struct Node{

int data;

struct Node *pre,*next;

}*Linklist;

创建链表:

Linklist head = (Linklist)malloc(sizeof(Linklist));

Linklist tail = (Linklist)malloc(sizeof(Linklist));

void createList(int n)

{

Linklist p,q;

head->pre = head->next = NULL;

tail->pre = tail->next = NULL;

q = head;

for(int i = 0; i < n; i++)

{

p = (Linklist)malloc(sizeof(Linklist));

scanf("%d",&p->data);

p->next = NULL;

p->pre = q;

q->next = p;

q = p;

}

p->next = tail;

tail->pre = p;

}

输出链表:

void next_print(Linklist head) //正向输出

{

Linklist p = head->next;

while(p && p != tail)

{

printf("%d ",p->data);

p = p->next;

}

printf("\n");

}

void pre_print(Linklist tail) //反向输出

{

Linklist p = tail->pre;

while(p && p != head)

{

printf("%d ",p->data);

p = p->pre;

}

printf("\n");

}

对于查找结点、修改结点、插入结点等等之类,与单链表基本一致,唯一不同的就是双链表可以选择正向遍历或者反向遍历两种方式。

接下来给出与单链表稍微有些许不同的删除操作:

删除结点:

void deleteList(Linklist head,int pos)

{

Linklist p = head;

int i = 0;

while(i < pos && p != tail) //遍历到当前结点

{

p = p->next;

i++;

}

p->pre->next = p->next; //前驱指向后继

p->next->pre = p->pre; //后继指向前驱

free(p);

p->next = p->pre = NULL;

}

循环链表

将一条链表的首尾相连我们就会得到一条循环链表。在单链表中,将尾结点的next域指向head结点,由此构造出一条单循环链表。在双链表中,将head结点的pre域指向尾结点,尾结点的next域指向head结点,由此构造出一条双循环链表。

循环链表有什么好处呢?首先,因为是循环结构,因此无须增加存储量,仅对表的连接方式稍作改变,即可使表的处理更加方便灵活。

① 循环链表没有NULL指针,因此在值判断操作中就不再是判别p == NULL 或者 p->next == NULL等方式,而是判别它们是否等于某一指针;

② 在单链表中实现全遍历只能从head节点开始,在双链表中只能从head或者tail结点开始,而在循环链表中可以从链表的任意位置开始;

单循环链表

单循环链表是在单链表的基础上实现的,在这里有两种实现方式,一种是带头结点的单循环链表,一种是带尾结点的单循环链表,两种方式均可实现,但有时候带尾结点的单循环链表可能更为方便。比方说:我们如果要查找链表的首元素和尾元素,那么用带头结点的单循环链表的话分别需要O(1)和O(n)的复杂的,因为虽然首元素可以直接得到,但尾元素却只能遍历得到,而如果采用带尾结点的单循环链表,则时间复杂度均是O(1)。

本来我是打算附上关于单循环链表一些基本操作的代码实现的,但敲了几个感觉实在是没什么用处,因为这些代码几乎和单链表的完全一样,所以这里我就略去了。

双循环链表

嗯。。。没啥需要说的,会写双链表就肯定会写双循环,双循环继承了双链表的许多优势同时又具有循环链表的优势,我就不再重复赘述了。

令附一表,在各种链表中实现相关基础操作的时间复杂度对比,包括单链表,已排序的单链表,双链表,以及已排序的双链表。

c7986d51014c6e5b060ae93ee5b2e5a1.png

关于链表还有很多值得说的地方,但碍于篇幅这篇就不再介绍了,下一篇再补充吧。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值