一、顺序表与链表的优缺点
顺序表缺陷:1.空间不够了要增容,增容要付出代码;原地扩容代价低,异地扩容代价高。
2.避免频繁扩容,满了基本都是扩2倍,可能会导致一定的空间浪费。
3.顺序表要求数据从开始位置连续存储,那么我们在头部活中间位置插入删除数据就需要挪动数据,效率不高。
顺序表的优点:支持随机访问,有些算法,需要结构支持随机访问,如:二分查找、优化的快排等。
针对顺序表的缺陷,设计出了链表。
链表的优点:按需申请空间,不用就释放(更合理的使用了空间);头部中间插入删除数据,不需要挪动数据; 不存在空间浪费。
链表的缺点:每一个数据,都要存一个指针去链接后面数据节点;不支持随机访问(用下标直接访问第i个)。
单链表的缺陷还是很多,单纯单链表增删查找的意义不大
1、很多题考察的都是单链表;
2、单链表更多的是去更复杂数据结构的子结构,如哈希桶,邻接表。
链表存储数据还要看双向链表。
二、单链表定义的相关代码
单链表尾插法
//单链表尾插法
void SListPushBack(SLTNode* phead SLTDateType x)
{
//找到尾结点
SLTNode* tail=phead;//从第一个结点开始
while(tail->next!=NULL)
{
tail=tail->next;//找到最后一个结点
}
SLTNode* newnode=(SLTNode*)malloc(sizeof(SLTNode));//开辟一个新结点存放x的值
newnode->data=x;/
newnode->next=NULL;//newnode存放完x的值后,作为最后一个结点,指向NULL
tail->next=newnode;//与链表连接
}
//SList.h文件
typedef int SLDateType;
typedef struct SListNode
{
SLdateType data;
struct SListNode* next;
}SListNode;
void SListPrint(SLTNode* phead);
void SListPushBack(SLTNode** pphead,SLTDateType x);
void SListPushFront(SLTNode** pphead,SLTDateType x);
void SListpopBack(SLTNode** pphead);
void SListpopFront(SLTNode** pphead);
SLTNode* SListFind(SLTNode* phead,SLTDateType x);
void SListInsert(SLTNode** phead,SLTNode* pos,SLTDateType x);
void SListInsertAfter(SLTNode* pos,SLTDateType x);
void SListErase(SLTNode** phead,SLTNode* pos);
void SListErase(SLTNode** phead,SLTNode* pos);
void SListDestory(SLTNode** phead);
#pragma once
#include <stdio.h>
typedef int SLTDateType;//SLDataType的作用等同于int,当数据的类型要变成char或者double时,只用把int换成char或者double
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SLTNode;
//输出链表中的数据
void SListPrint(SLTNode* phead)
{
SLTNode* cur=phead;//从第一个结点开始
while(cur!=NULL)
{
printf("%d->",cur->data);//打印数据
cur=cur->next;//指向下一个结点
}
}
//尾插法
void SListPushBack(SLTNode** pphead,SLTDateType x)
{
SLTNode* newnode=(SLTNode*)malloc(sizeof(SLTNode));
newnode->data=x;
newnode->next=NULL;//开辟一个数据的空间,把x放进去
if(*pphead==NULL)
{
*pphead=newnode;//链表为空直接插入
}
else {
SLTNode* tail=*pphead;
while(tail->next!=NULL)
{
tail=tail->next;//从头结点开始找,一直找到尾结点
}
tail->next=newnode;//把数据插在尾结点的后面
}
}
//头插法
void SListPushFront(SLTNode** pphead,SLTDateType x)
{
SLTNode* newnode=(SLTNode*)malloc(sizeof(SLTNode));
newnode->data=x;//开辟一个空间,存放X的值
newnode->next=NULL;
if(newnode==NULL)
{
printf("malloc fail\n");//打印数据
exit(-1);//指向下一个结点
}
newnode->next=*pphead;//把x放在头结点的前面
*pphead=newnode;//x此时的位置作为头结点
}
//尾删法
void SListpopBack(SLTNode** pphead)
{
assert(*pphead!=NULL);//不为空
if((*pphead)->next==NULL)
{
free(*pphead);
*pphead=NULL;//只有一个数据时,删除这个数据
}
SLTNode* pre=NULL;
SLTNode* tail=*pphead;
while(tail->next)
{
pre=tail;
tail=tail->next;//从头结点开始,一直向后,到尾结点,pre是tail的前一个结点
}
free(tail);//释放tail空间
tail=NULL;
pre->next=NULL;
}
//头删法
void SListpopFront(SLTNode** pphead)
{
assert(*pphead!=NULL);//不为空
if((*pphead)->next==NULL)
{
free(*pphead);//只有一个数据时,释放这个数据
*pphead=NULL;
}
else
{
SLTNode* pre=*pphead;
*pphead=(*pphead)->next;
free(pre);
}
}
//找数据
SLTNode* SListFind(SLTNode* phead,SLTDateType x)
{
SLTNode* cur=phead;
while(cur)
{
if(cur->data==x)
{
return cur;//找到x的位置,返回
}
else
{
cur=cur->next;//找不到x,一直往下走
}
}
return NULL;
}
//在pos位置前插入数据
void SListInsert(SLTNode** phead,SLTNode* pos,SLTDateType x)
{
SLTNode* newnode=(SLTNode*)malloc(sizeof(SLTNode));
newnode->data=x;
newnode->next=NULL;//开辟一个数据的空间,把x放进去
SLTNode* pre=*phead;
while(pre->next!=pos)
{
pre=pre->next;//找到pos前一个位置
}
pre->next=newnode;
newnode->next=pos;
}//复杂度O(N)
//在pos位置后插入数据
void SListInsertAfter(SLTNode* pos,SLTDateType x)
{
SLTNode* newnode=(SLTNode*)malloc(sizeof(SLTNode));
newnode->data=x;
newnode->next=NULL;//开辟一个数据的空间,把x放进去
newnode->next=pos->next;
pos->next=newnode;
}//复杂度O(1)
//删除结点
void SListErase(SLTNode** phead,SLTNode* pos)
{
SLTNode* pre=*phead;
if(*phead==pos)
{
*phead=pos->next;//让链表第二个变成头
free(pos);//释放掉第一个结点
}
else
{
while(pre->next!=pos)
{
pre=pre->next;
}
pre->next=pos->next;//pos的上一个结点指向pos的下一个结点
free(pos);
}
}
//删除该结点之后的结点
void SListEraseAfter(SLTNode** phead,SLTNode* pos)
{
assert(pos->next);
SLTNode* next=pos->next;
pos->next=next->next;
free(next);
}
//删除链表
void SListDestory(SLTNode** phead)
{
assert(phead);
SLTNode* cur=*phead;
while(cur)
{
SLTNode* next=cur->next;//不到尾就一直删
free(cur);
cur=next;
}
*phead=NULL;
}
int main(void)
{
return 0;
}
三、单链表部分例题
1.移除链表元素(力扣)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeElements(struct ListNode* head, int val){
if(head==NULL)
{
return NULL;
}
struct ListNode* pre=NULL;
struct ListNode*cur=head;
while(cur)//cur不为空,一直往后走
{
if(cur->val==val)
{
if(head->val==val)//如果头部等于val,头删
{
head=cur->next;//让链表第二个变成头
free(cur);//释放掉第一个结点
cur=head;//cur等于当前的头
}
else
{
pre->next=cur->next;//头部数据不等于val,但找到了数据为val的位置,让pre的下一个指向cur的下一个结点值
free(cur);//释放掉cur结点的值
cur=pre->next; //让cur指向pre的下一个位置
}
}
else
{
pre=cur;//没有找到数据为val的位置
cur=cur->next;//继续往后走
}
}
return head;//返回新的头
}
2.反转链表(力扣)
①指针翻转
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* reverseList(struct ListNode* head){
if(head==NULL)
{
return NULL;
}
struct ListNode* n1,*n2,*n3;
n1=NULL;
n2=head;//n2一开始指向头结点
n3=head->next;//n3一开始指向第二个结点
while(n2)
{
//翻转
n2->next=n1;//第一个结点指向n1即空
//迭代走
n1=n2;//原来的第一个结点变为n1
n2=n3;//原来的第二个结点变为n2
if(n3)
n3=n3->next;//n3变为原来n3的下一个
}
return n1;
}
② 头插(取原链表中结点,头插到newhead新链表中)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* reverseList(struct ListNode* head){
struct ListNode* cur=head;
struct ListNode* newhead=NULL;
while(cur)
{
struct ListNode* next=cur->next;
//头插
cur->next=newhead;
newhead=cur;
cur=next;
}
return newhead;
}
3.找到链表的中间结点(力扣)
/**
*Definition for singly-linked list.
*struct ListNode{
int val;
struct ListNode *next;
*};
*/
//快慢指针,慢指针一次走一个结点,快指针一次走两个结点。当链表节点数为奇数时,快指针走完链表的时候,慢指针恰好走到中间;当为偶数时,快指针走到空时,慢指针走到中间
struct ListNode* middleNode(struct ListNode* head)
{
struct ListNode* slow,*fast;
slow=fast=head;
while(fast&&fast->next)//当fast为空或者fast->next为空时结束
{
slow=slow->next;//slow一次走一个结点
fast=fast->next->next;//fast一次走两个结点
}
return slow;
}
4.返回链表的倒数第k个结点(牛客)
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param head ListNode类
* @param n int整型
* @return ListNode类
*/
struct ListNode* removeNthFromEnd(struct ListNode* head, int k ) {
struct ListNode* fast,*slow;
slow=fast=head;
while(k--)//......k
//while(--k)......k-1
//fast先走k步,slow再走,当fast走到尾时,slow走到倒数第k个
{
//k大于链表的长度,返回空
if(fast==NULL)
{
return NULL;
}
fast=fast->next;//fast走k步
}
while(fast)
{
fast=fast->next;
slow=slow->next;//fast走到k之后fast和slow一起走,直到fast走到空
}
return slow;
}
5.合并两个有序链表(力扣)
1.判断l1和l2是否为空;
2.定义头结点和尾结点;
3.判断l1和l2的大小,尾插;
4.判断是否第一次尾插;
5.判断l1和l2谁先结束;
6.返回头结点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
//其中一个链表为空,返回另外一个
if(l1==NULL)
{
return l2;
}
if(l2==NULL)
{
return l1;
}
struct ListNode* head=NULL,*tail=NULL;
while(l1&&l2)//只要有一个为空就停止
{
if(l1->val<l2->val)
{
if(head==NULL)//第一次尾插
{
head=tail=l1;//把l1插到尾
}
else
{
tail->next=l1;//不是尾,插到尾
tail=l1;//l1变为尾
}
l1=l1->next;//l1往后走
}
else
{
if(head==NULL)
{
head=tail=l2;
}
else
{
tail->next=l2;
tail=l2;
}
l2=l2->next;//l2小,l2小往后走
}
}
if(l1)
{
tail->next=l1;//l2结束,把l1链接到tail后面
}
if(l2)
{
tail->next=l2;//l1结束,把l2链接到tail后面
}
return head;
}
6.链表的分割(力扣)
(a) (b)
(c) (d)
(a)(b)(c)(d)表示链表数据的整个存放过程,下面这个图则表示将两个链表合并起来,即将lessTail->next指向greatHead->next。
如果greatTail不指向空的话,就会出现如下所示的死循环 ,因此greatTail->next=NULL。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* partition(struct ListNode* head, int x){
struct ListNode* lessHead,*lessTail,* greatHead,*greatTail;
//开一个哨兵位头结点,方便尾插,先分为两个链表,less里存放比X小的值,great存放比X大的值
lessHead=lessTail=(struct ListNode*)malloc(sizeof(struct ListNode));
lessTail->next=NULL;
greatHead=greatTail=(struct ListNode*)malloc(sizeof(struct ListNode));
greatTail->next=NULL;
struct ListNode* cur=head;
while(cur)
{
if(cur->val<x)
{
lessTail->next=cur;//值比X小,放在less里
lessTail=cur;//cur变成队尾
}
else
{
greatTail->next=cur;//值比X大,放在great里
greatTail=cur;//cur变成队尾
}
cur=cur->next;//指向下一个
}
lessTail->next=greatHead->next;//合并两个链表
greatTail->next=NULL;//防止出现死循环
struct ListNode* newhead=lessHead->next;//保存头结点
free(lessHead);
free(greatHead);
return newhead;
}
7.相交链表(牛客网)
判断两个链表是否相交;如果相交,求交点。
①暴力求解(穷举):依次取A链表中的每个结点跟B链表中的所有结点比较,如果有地址相同的结点,就是相交,第一个相同的交点。
②要求优化到O(n):尾结点相同就是相交,否则就不相交;长的链表先走(长度差步),再同时走,第一个相同就是交点。
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
struct ListNode* tailA=headA;
struct ListNode* tailB=headB;
int lenA=1;
while(tailA->next)
{ lenA++;//算出链表A的长度
tailA=tailA->next;//找到A的尾
}
int lenB=1;
while(tailB->next)
{
lenB++;//算出链表B的长度
tailB=tailB->next;//找到B的尾
}
if(tailA!=tailB)//不相交
{
return NULL;
}
int gap=abs(lenA-lenB);
//长的先走差距步,再同时走找交点
struct ListNode* longList=headA;
struct ListNode* shortList=headB;
if(lenA<lenB)
{
shortList=headA;
longList=headB;//如果B长,longList=headB;
}
while(gap--)
{
longList=longList->next;
}
while(longList!=shortList)
{
longList=longList->next;
shortList=shortList->next; //没有相交的,两个链表就往后走
}
return longList;
}
8.复制带随机指针的链表(力扣)
① 复制结点,插入到原结点和下一个结点之间;
② 根据原结点random,处理复制结点的random;
③ 复制结点解下来链接成一个新链表,恢复原链表链接关系。
struct Node* copyRandomList(struct Node* head) {
//1.拷贝结点插入原结点的后面
struct Node* cur=head;
while(cur)
{
struct Node* copy=(struct Node*)malloc(sizeof(struct Node));
copy->val=cur->val;//copy的值等于原结点的值
//插入copy结点
copy->next=cur->next;//第二个图
cur->next=copy;
cur=copy->next;//第三个图
}
//2.根据原结点,处理copy结点的random
cur=head;
while(cur)
{
struct Node* copy=cur->next;
if(cur->random==NULL)
{
copy->random==NULL;
}
else
{
copy->random=cur->random->next;
}
cur=copy->next;
}
//3.把拷贝结点解下来,链接成新链表,同时恢复原链表。
struct Node* copyHead=NULL,*copyTail=NULL;
cur=head;
while(cur)
{
struct Node* copy=cur->next;
struct Node* next=copy->next;
if(copyTail==NULL)
{
copyHead=copyTail=copy;//头插
}
else
{
copyTail->next=copy;//尾插
copyTail=copy;//copyTail往后走
}
cur->next=next;//恢复原链表
cur=next;
}
return copyHead;
}