文章目录
1、双向带头循环链表
线性表=有n个元素构成的集合,逻辑结构呈现线性。
带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势;增加头结点的目的是算法实现上的方便:在头部进行插入 删除时,可以和其他节点写法一样,统一起来,不用判断是否为链表第一个节点。
head不是存放有效数据的节点,如果只有一个head节点,说明链表为空。
带头结点的双循环链表:末尾插入结点和删除尾结点最节省时间,找尾:头节点的下一个;单链表需要找到前驱节点,然后才能删除尾,找尾:一个一个往后走:耗时
List.c
#include "List.h"
//理解不了则写代码,代码写多了则理解,看懂之后再不看别人写的自己“默写”
//双向带头循环链表
//方法1:
void ListPushBack(ListNode*phead, LTDataType x){
assert(phead);
ListNode*tail = phead->prev;
ListNode*newnode = BuyListNode(x);
//把3个节点(phead\tail\newnode)链接起来
tail->next = newnode; newnode->prev = tail;
newnode->next = phead; phead->prev = newnode;
}
//方法2:
void ListPushBack(ListNode*phead, LTDataType x){
ListInsert(phead, x); //尾插:phead的前1个=尾
}
void ListPopBack(ListNode* phead){
//方法2:
assert(phead);
assert(phead->next != phead);
ListNode*tail = phead->prev;//tail局部变量
ListNode*tailPrev = tail->prev;
tailPrev->next = phead; phead->prev = tailPrev;
free(tail); tail = NULL;
//方法1:
ListErase(phead->prev);
}
void ListPushFront(ListNode* phead, LTDataType x){
//方法2:
assert(phead);//头插=第1个有效数字前面插入
ListNode*first = phead->next;
ListNode*newnode = BuyListNode(x);
phead->next = newnode; newnode->prev = phead;
newnode->next = first; first->prev = newnode;
//方法1:
ListInsert(phead->next,x);
}
void ListPopFront(ListNode* phead){
//方法2:
assert(phead);
assert(phead->next != phead);//没有节点了,再删把头节点都删了
ListNode*first = phead->next;
ListNode*second = first->next;
phead->next = second; second->prev = phead;
free(first);
//方法1:
ListErase(phead->next);
}
ListNode*ListFind(ListNode* phead, LTDataType x){
assert(phead);
ListNode*cur = phead->next;
while (cur!=phead){
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
void ListInsert(ListNode* pos, LTDataType x){
assert(pos);//3个(posprev newnode pos)之间链接,pos的前面插入
ListNode*posPrev = pos->prev;
ListNode*newnode = BuyListNode(x);
posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
}
void ListErase(ListNode* pos){
assert(pos);
//assert(pos!=phead);//不允许删除头
ListNode*posPrev = pos->prev;
ListNode*posNext = pos->next;
free(pos);
//posPrev pos posNext 3个节点
posPrev->next = posNext;
posNext->prev = posPrev;
}
ListNode* BuyListNode(LTDataType x){
ListNode* node = (ListNode*)malloc(sizeof(ListNode));
node->data = x;
node->next = NULL;
node->prev = NULL;
//不=空则为随机值;除了静态变量、全局变量,如果不初始化,则为随机值
return node;
}
void SListPrint(ListNode* phead){
assert(phead);
ListNode* cur = phead->next;
while (cur != phead){//循环链表cur永远不会=空,cur = phead时,即遍历完所有节点
printf("%d->", cur->data);
cur = cur->next;
}printf("\n");}
void ListInit(ListNode** pphead){//方法1:是指针,则要传指针phead的地址才能改变
*pphead = BuyListNode(0);
(*pphead)->next = *pphead;
(*pphead)->prev = *pphead;
}
//方法2:返回1个指针
ListNode*ListInit(){
ListNode*phead = BuyListNode(0);
(phead)->next = phead;
(phead)->prev =phead;
return phead;
}
void ListClear(ListNode*phead){
//把插入的数字都删除,保留头结点,可以继续使用,准备输入新数字,传1级指针
assert(phead);
ListNode*cur = phead;
while (cur!=phead){
ListNode*next=cur->next;
free(cur); cur = next;
}
(phead)->next = phead; //链表永远要保持带头双向循环
(phead)->prev = phead;
}
void ListDeatory(ListNode**pphead){//销毁,传2级指针
assert(*pphead);
ListClear(*pphead);//清除数据
free(*pphead); //清除头
*pphead = NULL;
}
List.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode*next;
struct ListNode*prev;
LTDataType data;
}ListNode;
ListNode* BuyListNode(LTDataType x);
void ListPrint(ListNode* phead);
ListNode*ListInit();
void ListClear(ListNode* phead);
void ListDestory(ListNode** pphead);
void ListPushBack(ListNode*phead, LTDataType x);
void ListPopBack(ListNode* phead);
void ListPushFront(ListNode*phead, LTDataType x);
void ListPopFront(ListNode* phead);
ListNode*ListFind(ListNode* phead, LTDataType x);
void ListInsert(ListNode* pos, LTDataType x); //在pos前面插入X
void ListErase(ListNode* pos);
test.c
#include"List.h"
void test1(){
ListNode*phead = ListInit();
ListPushBack(phead, 1);
ListPushBack(phead, 2);
ListPrint( phead);
ListPopBack( phead);
ListPrint(phead);
ListPushFront(phead,3);
ListPrint(phead);
ListPopFront(phead);
ListPrint(phead);
ListDestory(& phead);
}
void test2(){
ListNode*phead = ListInit();
ListPushBack(phead, 1);
ListPushBack(phead, 2);
ListPrint(phead);
ListNode*pos = ListFind(phead, 2);
pos->data = 300;//把2改为300
ListInsert(pos, 30);//在2的前面插入30
ListPrint(phead);
ListErase(pos);
ListPrint(phead);
ListDestory(&phead);
}
int main()
{test1();
return 0;}
2、链表和顺序表(数组)的区别和联系:
顺序表
就是在数组的基础上实现增删查改,并且插入时可以动态增长
顺序表缺陷:
a、可能存在一定空间浪费,增容一般是呈2倍的增长
b、增容有一些效率损失,需要开辟空间、释放空间
c、中间或者头部插入删除,时间复杂度为O(N),因为要挪动数据。
这些问题链表来解决。
线性表=有n个元素构成的集合,逻辑结构呈现线性
栈一般用顺序表实现,栈只能在一端插入和删除数据
链表
优点:用1个,则申请1个;
2.现实中的结点一般都是从堆上申请出来的
3.从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续
队列一般用链表实现。链栈,一般需要进行头插(头删)操作,而顺序栈一般进行尾插(头删)操作。
链表和顺序表(数组)
是互补的数据结构,链表是因为数组的缺陷(定长的)而产生的,
3、单链表
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
1
2
3
test.c
无环
#include"SList.h"
int main()
{
SListNode* pList=NULL;//头指针
SListPushBack(&pList, 1);
SListPushBack(&pList, 2);
//SListPopBack(&pList); //有问题,为什么
SListPushFront(&pList, 3);
SListPushFront(&pList, 4);
SListPopFront(&pList);
SListNode*pos = SListFind(pList,3);//把3改为30,数组给的是下标,链表给的是节点指针=地址
if (pos){
pos->data = 30;
}
SListInsertAfter(SListFind(pList,2), 6);
SListEraseAfter(SListFind(pList, 3));
SListPrint(pList); SListDestroy(&pList);
SListReversePrint( pList);
/*方法1时:
ListNode*phead = NULL;
ListInit(&phead);*/
return 0;
}
有环
SListNode* HasCircle(SListNode* L)
{
SListNode* fast = L; SListNode* slow = L;
while (fast && fast->next)
{
fast = fast->next->next; //1次走2步
slow = slow->next;
if (fast == slow)
return fast;//带环
}return NULL;//不带环
}
SListNode* CheckListCross(SListNode*, SListNode* L2)//返回交点
{
SListNode* pM1 = HasCircle(L1);
SListNode* pM2 = HasCircle(L2);
if (NULL == pM1 && NULL == pM2)
{
// 两个链表都不带环
}
else if (pM1 & & pM2){
//两个链表都带环
}return NULL;
}
void TestsList2()
{
SListNode* pList1 = NULL;
SListPushBack(&pList1, 1);
SListPushBack(&pList1, 2);
SListPushBack(&pList1, 3);
SListPushBack(&pList1, 4);
SListFind(pList1, 4)->next = SListFind(pList1, 2);//带环链表
SListNode* pList2 = NULL;
SListPushBack(&pList2, 11);
SListPushBack(&pList2, 22);
SListPushBack(&pList2, 33);
SListFind(pList2, 33)->next = SListFind(pList1, 3);//交点在环外
}
SList.h
//声明
#pragma once
#include <stdio.h>
#include <stdlib.h>
typedef int SListDataType;
typedef struct SListNode
{
SListDataType data;
struct SListNode*next;//指向下一个节点的4字节指针变量
}SListNode; //SListNode=SLTNode,cur++=+1个结构体大小
SListNode* BuySListNode(SListDataType x);
void SListPushBack(SListNode** phead, SListDataType x);
void SListPopBack(SListNode** phead);
void SListPushFront(SListNode** phead,SListDataType x);
void SListPopFront(SListNode** phead);
SListNode*SListFind(SListNode* phead, SListDataType x);
void SListPrint(SListNode* phead);//不改变外面指针,不需要2级指针,当然也可以使用2级指针
void SListInsertAfter(SListNode* pos, SListDataType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos);
void SListReversePrint(SListNode* plist);
void SListDestroy(SListNode** plist);
void TestSList1();
SList.c
#include "SList.h"
#include <malloc.h>
#include <assert.h>
#include <stdio.h>
SListNode* BuySListNode(SListDataType x)
{
SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
if (newNode == NULL)
{
printf("申请结点失败\n");
exit(-1);
}
newNode->data = x;
newNode->next = NULL;//不值空则为随机值
return newNode;
}
void SListPushBack(SListNode** pphead, SListDataType x)
{//传指针的地址,2级指针
SListNode* newNode = BuySListNode(x);
if (*pphead == NULL)//链表=空
{
*pphead = newNode;//*解引用
}
else
{
//找尾,尾的next是空,->解引用
SListNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newNode;
}
}
void SListPopBack(SListNode** pphead)
{
// 1、空
// 2、一个结点
// 3、一个以上结点
if (*pphead == NULL)
{
return;//必须先判断*pphead = NULL,否则如果头=空,(*pphead)->next == NULL判断时=空的next的判断=崩
}
else if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SListNode* prev = NULL;
SListNode* tail = *pphead;//tail局部变量,1个节点以上节点
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
prev->next = NULL;
}
}
void SListPushFront(SListNode** pphead, SListDataType x)//头插
{
SListNode* newnode = BuySListNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
void SListPopFront(SListNode** pphead)
{//传plist的地址过来,* pphead=plist的值=第1个节点的地址;1空2一个节点3一个以上节点
// 1.空
// 2.一个节点 + 3.一个以上的节点
if (*pphead == NULL)
{
return;//如果是空则不删
}
else
{
SListNode* next = (*pphead)->next;
free(*pphead);//free释放:指针指向的内存,不再拥有使用权,释放空间并且置为随机值;内存泄露:指针丢了
*pphead = next;
}
}//合并多个节点+1个节点的情况
void SListPrint(SListNode* phead)
{
SListNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
// 单链表查找
SListNode* SListFind(SListNode* phead, SListDataType x)
{
SListNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;//没有找到则返回空
}
void SListInsertAfter(SListNode* pos, SListDataType x)
{
assert(pos);
SListNode* newnode = BuySListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
void SListEraseAfter(SListNode* pos)//删除pos位置之后的值
{
assert(pos);
if (pos->next)
{
SListNode* next = pos->next;
SListNode* nextnext = next->next;
pos->next = nextnext;
free(next);
}
}//逆序打印链表 方法1:
void SListReversePrint(SListNode* plist){
if (NULL != plist){
SListReversePrint(plist->next);
printf("%d ", plist->data);
}
}
//方法2
void SListDestroy(SListNode** plist)
{
assert(plist);
SListNode* cur = *plist;
while (cur)
{
*plist = cur->next; free(cur);
cur = *plist;
}
*plist = NULL;
}
逆序打印链表
4、顺序表
方法2:运用栈
seqList.C
#include "seqList.h"
#include <stdio.h>
#include <malloc.h>
#include <assert.h>
//将ps指向的结构体变量初始化成功
void SeqListInit(SeqList* ps, int initCapacity){
assert(ps) ;
ps->array = (int* ) malloc(initCapacity*sizeof (int) ) ;
if(NULL==ps->array){
assert (0) ;
return;
}
ps->capacity = initCapacity;ps->size = 0 ;}
void seqListDestroy (SeqList* ps){
assert (ps);if (ps->array){
free(ps->array) ;ps->array = NULL;ps->capacity =0;
ps->size=0;}}
void seqListPrint (seqList* ps){
assert (ps) ;
for (int i=0;i<ps->size;++i){
printf ( "%d ", ps->array[i] );
}}
int seqListEmpty (SeqList* ps){
assert (ps) ;
return 0== ps->size;}//是空:返回真
void seqListPushBack (SeqList* ps,DataType data){//只要是插入都检测空间是否足够
assert(ps) ;ChecKCapacity(&ps);
ps->array[ps->size] =data;ps->size++;}
void seqListPopBack(seqList*ps){
assert (ps) ;
//如果顺序是空的---直接返回
if (SeqListEmpty (ps)) {
return;}
ps->size--;}
int seqListsize (seqList* ps){
assert(ps) ;
return ps->size;
}
//空间总的大小:
int seqListCapacity (segList* ps){
assert(ps) ;
return ps->capacity;}
int seqListFind (seqList* ps,DataType data){
assert(ps) ;
for (int i = 0; i< ps->size; ++i){
if(ps->array [i]==data){
return i;
}
}return -1;
}
void seqListPushFront(SeqList*ps, DataType data){//时间复杂度=O(N)
assert (ps) ;ChecKCapacity(&ps);
//将顺序表中所有元素整体往后搬移一个位置
for(int i=ps->size - 1; i >=0 ; --i)
{ps->array [i+1]=ps->array [i ];
}
//3.插入元素
ps->array [0] = data;
ps->size++;
}
void seqListPopFront (seqList* ps){
if (SeqListEmpty (ps)){
return;
)
//将顺序表中的元素除第一个外整体往前搬移一步
for (int i = 0; i < ps->size - 1; i++){
ps->array [i] =ps->array [i+1];
}
ps->size--;}
void SeqListInsert(SeqList* ps, size_t pos, DataType data){
assert(ps);
if (pos < 0 || pos > ps->size)
{
return;
}ChecKCapacity(&ps);
for (int i = ps->size - 1; i >= pos; i--){
ps->array[i+1] = ps->array[i];
}
ps->array[pos] = data; ps->size++;
}
void seqListErase(SeqList* ps, size_t pos){
assert(ps);
if (pos < 0 || pos >= ps->size){
printf("位置有问题\n"); return;
}
for (int i = pos + l; i < ps->size; ++i){
ps->array[i - 1] = ps->array[i];
}
ps->size--;
}
//获取顺序表中最后一个元素
DataType seqListBack (SeqList* ps){
assert(ps) ;
return ps->array[ps->size-1];}
void ChecKCapacity(seqList* ps){
assert(ps);
if (ps->size == ps->capacity)
{
int newCapacity = (ps->capacity << 1);
DataType* temp = (DataType*)malloc(sizeof (DataType)*newCapacity);
if (NULL == temp)
{
return;
}
for (int i = 0; i < ps->size; ++i){
temp[i] = ps->array[i];
}
free(ps->array);
ps->array = temp;
ps->capacity = newCapacity;
}
}
void TestseqList1(){
SeqList s;
SeqListInit ( &s,10) ;
seqListInit ( &s,10);seqListPushBack ( &s,1);
SeqListDestroy ( &s) ;
}
//测试顺序表
void TestSeqList (){
TestSeqList1 ();
}
seqList.h
typedef int DataType;
//动态顺序表:底层的空间从堆上动态申请出来的
typedef struct SeqList{
int* array;//指向存储元素空间的起始位置
int capacity;//表示空间总的大小
int size;//有效元素的个数
} seqList;
void seqListInit (SeqList* ps, int initCapacity);
void seqListDestroy (seqList* ps);I
void SeqListPrint (SeqList* ps) ;
void TestSeqList ( );
主函数:
include <stdio.h>
#include "SeqList.h"
int main ( )
{
TestSeqList ( );return 0;
}