本文主要是回顾之前所学的链表知识,重点介绍单链表。
一:链表是线性表的链接存储方式:主要有单链表、静态链表、双向链表、循环链表等。
二:1、链表节点可以连续可以不连续存储;
2、结点的逻辑顺序和物理顺序可以不一致;
3、表可以扩充
三:什么是带表头结点的单链表? 与不带表头结点的单链表有什么区别?
1、表头结点在表的最前端,本身不带数据,有一个头指针指向表头,仅标识表头
2、带表头结点有什么好处:可以统一空表与非空表的操作、简化链表操作的实现
3、两者区别其实不大只是一个有head头指针一个没有,在链表的初始化、插入、删除,输出等判断条件不一样
一个是whie(head->next != NULL) 而另一个直接是 while(head != NULL)。
四:简单介绍下循环链表:循环链表的最后一个结点指针不指向NULL,而是指向了表的前端,为简化操作往往加入表头结点
它的特点是只要知道其中某个结点的地址就可搜寻到其他所有节点的地址
五:双向链表:双向链表是指在前驱和后继方向都能遍历的线性表
六:顺序表和链表有什么区别呢:
存储分配的方式
顺序表的存储空间是静态分配的;链表的存储空间是动态分配的;
存储密度 = 结点数据本身所占的存储量/结点结构所占的存储总量,顺序表的存储密度 = 1,链表的存储密度 < 1 (基于空间的比较)
顺序表可以随机存取,也可以顺序存取,链表是顺序存取的;
插入或删除 时移动元素个数:顺序表需要移动近一半元素; 链表不要需要移动元素,只需要修改指针;(基于时间的比较)
若插入或删除仅发生在表的两端,宜采用带尾指针的循环链表
七:下面我主要介绍下单链表的插入、删除、遍历、销毁,逆置等操作
1、首先 单链表的有头插,尾插法,还有中间插入等
头插法:
void CreateList(LinkList *L, ElemType a[], int n)
{
LinkList s;
int i;
(*L) = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL;
for (i = 0; i < n; i++)
{
s = (LinkList)malloc(sizeof(LinkList));
s->data = a[i];
s->next = (*L)->next;
(*L)->next = s;
}
}
头插法顾名思义就是在第一个结点之前头结点之后插入结点;先给结点分配空间,然后赋值 ,接着使结点s->next指向的结点为头指针L->next指向的结点,所以头指针L->next就等于指针s
尾插法:
void CreateListW(LinkList *L, ElemType a[], int n) //尾插法
{
LinkList s,r;
int i;
(*L) = (LinkList)malloc(sizeof(Node));
(*L) ->next = NULL;
r = (*L);
for (i = 0; i < n; i++)
{
s = (LinkList)malloc(sizeof(Node));
s->data = a[i];
r->next = s;
r = s;
}
r->next = NULL;
}
尾插法就是在尾部一个一个插,尾指针要不断地往后移,所以要定义一个尾指针r,使r->next = s;r = s;
在中间插入:
int ListInsert(LinkList *L, int i, ElemType e)
{
LinkList p = *L;
int j = 1;
while(p != NULL && j < i )
{
p = p->next;
j++;
}
if(p == NULL || j > i)
{
return FAILURE;
}
LinkList n = (LinkList)malloc(sizeof(Node));
if(NULL == n)
{
return FAILURE;
}
n->data = e;
n->next = p->next;
p->next = n;
return SUCCESS;
}
在中间任意一个位置前插入的话要注意指针p的指向,p应指向要插入位置的前一个结点,所以要定义一个变量 j = 1 进行判断
给要插入的结点赋值,插入结点的next指针指向应为他前一个结点指针next指向;那么此时前一个结点指针的next指向应为插入的结点指针n;
删除链表的摸个结点:
int ListDelete(LinkList L, int i,ElemType *e)
{
LinkList p = L;
int j = 1;
LinkList tmp;
while(p != NULL && j < i)
{
p = p->next;
j++;
}
if(p == NULL || j >i)
{
return FAILURE;
}
tmp = p->next;
p->next = tmp->next;
free(tmp);
return SUCCESS;
}
链表结点删除与链表结点插入差不多:
首先都是定义一个 j = 1,通过while确定p的位置应指向删除结点的前一个位置,然后要定义一个指针变量tmp来存储要删除结点的指针,此时p->next = tmp->next了
然后释放要删除结点也就是 tmp;
链表遍历就很简单了
int ListTraverse(LinkList L, void(*print)(ElemType))
{
if(NULL == L)
{
return FAILURE;
}
LinkList p = L->next;
while(p)
{
print(p->data);
p = p->next;
}
return SUCCESS;
}
注意这边的参数为函数指针 print 这儿指针指向一个函数负责输出 通过 while循环遍历
链表销毁
void DestroyList(LinkList *L)
{
LinkList p = (*L);
LinkList q = (*L)->next;
while(q != NULL)
{
free(p);
p = q;
q= p->next;
}
free(p);
}
销毁链表先定义一个头指针,再定义第一个结点的指针,然后先释放头结点,接着让 p = q;q = p->next;同步后移一个一个删除。
链表的逆置:
int Reverse_List(Node *h)
{
// h->next 空表
// h->next->next 只有一个结点
if (h == NULL || h->next == NULL || h->next->next == NULL)
return FALSE;
Node *pre = h->next;
Node *cur = h->next->next;
Node *tmp;
while (cur)
{
tmp = cur->next;
cur->next = pre;
pre = cur;
cur = tmp;
}
h->next->next = NULL;
h->next = pre;
return TRUE;
}
好了我的链表知识的梳理归纳就到这了,有什么不足的地方,希望大家多多指教!