学习目标:
- 链表
- 单链表
- 定义
- 基本操作的实现
- 双链表
- 循环链表
- 静态链表
- 单链表
两种线性表的存储方式的优缺点
- 顺序表
优点: 可以随机存取,存储密度高
缺点: 要求大片的连续空间,改变容量不方便 - 单链表
优点: 不要求大片连续存储空间,改变容量方便
缺点: 不可随机存取,要耗费一定的空间存放指针
用代码定义单链表
/**
* 传统定义方法
*/
//struct LNode{
// int data;
// struct LNode *next;
//};
//typedef struct LNode LNode;
//typedef struct LNode *LinkList;
/**
* 课本上的定义方法
*/
typedef struct LNode{
int data;
struct LNode *next;
}LNode, *LinkList;
获取节点
LNode *getElem(LinkList L, int i) {
int j = i;
LNode *p = L->next;
if (i==0){
return L;
}
if (i<1){
return NULL;
}
while (p!= NULL && j <i){
p = p->next;
j++;
}
return p;
}
初始化一个不带头指针的单链表
/**
* 初始化一个空的单链表
*/
bool InitList(LinkList &L){
L = NULL; //空表,暂时没有任何节点-->防止脏数据
return true;
}
void test(){
LinkList L;
InitList(L);
}
初始化一个带头指针的单链表
/**
* 初始化一个带头节点的单链表
* @param L
* @return
*/
bool InitList1(LinkList &L){
L = (LNode *) malloc(sizeof (LNode));
if (L == NULL){
return false;
}
L->next = NULL;
return true;
}
void test1(){
LinkList L;
InitList1(L);
}
不带头节点 V.S. 带头结点
不带头节点的代码操作更加复杂,处理第一个数据节点和后续节点的处理需要不同的代码逻辑。
LinkList — 强调这是一个单链表
Lnode* — 强调这是一个节点
单链表的插入和删除
- 插入
- 按位序插入
- 指定节点的后插操作
- 带头节点
- 不带头节点
- 指定节点的前插操作
- 删除
- 按位序删除
- 指定节点的删除
按位序插入(带头结点)
ListInsert(&L, i, e)
/**
* 在带头结点的单链表中插入元素
* 在第i个位置插入元素e
* @param L
* @param i
* @param e
* @return
*/
bool ListInsertDaiTouJieDian(LinkList &L, int i, int e) {
if (i < 1) {
return false;
}
LNode *p;
int j = 0;
p = L;
while (p != NULL && j < i-1){
p = p->next;
j++;
}
if (p==NULL){
return false;
}
LNode *s = (LNode *) malloc(sizeof (LNode));
s->data = e;
s->next = p->next;
p->next=s;
return true;
}
时间复杂度 O(n)
按位序插入(不带头节点)
/**
* 笔袋头节点的单链表,按位序插入
* ps:如果不带头结点,删除第一个元素时,需要改变头指针L;
* @param L
* @param i
* @param e
* @return
*/
bool ListInsertBuDaiTouJieDian(LinkList &L, int i, int e) {
if (i < 1) {
return false;
}
if (i == 1) {
LNode *s = (LNode *) malloc(sizeof(LNode));
s->data = e;
s->next = L;
L = s;
return true;
}
LNode *p;
int j = 1;
p = L;
while(p!= NULL && j < i-1){
p = p->next;
j++;
}
if (p == NULL){
return false;
}
LNode *s = (LNode * ) malloc(sizeof (LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
指定节点后的后插操作
/**
* 后插操作
* 在第i个位置后进行插入操作
* @param L
* @param i
* @param e
* @return
*/
bool ListInsertAnWeiZhi(LinkList &L, int i, int e) {
if (i < 1) {
return false;
}
LNode *p;
int j = 0;
p = L;
while (p != NULL && j < i - 1) {
p = p->next;
j++;
}
if (p == NULL) {
return false;
}
LNode *s = (LNode *) malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
/**
* 后插操作
* 在指定节点后进行操作
* @param p
* @param e
* @return
*/
bool ListInsertNextNode(LNode *p, int e) {
if (p == NULL) {
return false;
}
LNode *s = (LNode *) malloc(sizeof (LNode));
if (s == NULL){ //内存分配失败
return false;
}
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
指定节点的前插操作
/**
* 前插操作
* 偷天换日
* 由于不知道指定节点的前方节点,只能在后方节点后插入,然后更换数据
* @param p
* @param e
* @return
*/
bool ListInsertPriorNode(LNode *p, LNode *s){
if (p==NULL){
return false;
}
// LNode *s = (LNode *) malloc(sizeof (LNode));
// if (s== NULL){
// return false;
// }
s->next = p->next;
p->next = s;
int temp = p->data;
p->data = s->data;
p->data = temp;
return true;
}
按位序删除(带头结点)
/**
* 删除带头节点的单链表中的节点
* @param L
* @param i
* @param e
* @return
*/
bool ListDeleteDaiTouJieDian(LinkList &L, int i, int &e) {
if (i < i) {
return false;
}
LNode *p;
int j = 0;
p = L;
while (p != NULL && j < i - 1) {
p = p->next;
j++;
}
if (p==NULL){
return false;
}
if(p->next == NULL){
return false;
}
LNode *q = p->next;
e = q->data;
p->next = q->next;
free(q);
return true;
}
时间复杂度O(n)
指定节点的和删除
/**
*删除指定节点
* 需要注意的是不知道前驱节点,可以知道后继节点
* 采用更改数据域的内容删除该节点
* @param p
* @return
*/
bool DeleteNode(LNode *p) {
if (p == NULL) {
return false;
}
LNode *q = p->next;
p->data = p->next->data;
p->next = q->next;
free(q);
return true;
}
时间复杂度 O(1)
单链表的局限性
无法逆向检索