第一章:线性表
1.1顺序表
顺序表类型定义
#define MAXSIZE 100
typedef struct
{
Elemtype data[MAXSIZE];
int length;
}Sqlist;
建立顺序表
数组a[0…n-1],顺序表L。
i遍历数组a,k遍历顺序表。
void createlist(Sqlist* &L, Elemtype a[], int n)
{
int i=0,k=0;
L=(Sqlist *)malloc(sizeof(Sqlist));
while(i<n)
{
L->data[k]=a[i];
k++;i++;
}
L->length=k;
}
初始化线性表
设置长度length=0
void InitList(Sqlist* &L)
{
L=(Sqlist *)malloc(sizeof(Sqlist));
L->length=0;
}
销毁线性表
释放free
void DestroyList(Sqlist* &L)
{
free(L);
}
判断空表
不需要返回表L,传参不需要引用&
void ListEmpty(Sqlist* L)
{
if(L->length==0)
{
return true;
}
else
{
return false;
}
}
或者
bool ListEmpty(Sqlist* &L)
{
return (L->length==0);
}
返回一个bool型变量。
求线性表长度
int ListLength(Sqlist* L)
{
return (L->length);
}
输出线性表
void DispList(Sqlist* L)
{
int i;
if(ListEmpty(L)) return;
for(i=0;i<L->length;i++)
{
cout << L->data[i] << endl;
}
}
求表中某个元素e
需要返回e,所以e带上引用&.
注意 e=L->data[i-1]中,
当顺序表/数组的逻辑下标为i时,物理下标为i+1.
bool GetElem(Sqlist* L, int i, Elemtype &e)
{
if((i<1) || (i>L->length))
{
return false;
}
e=L->data[i-1];
return true;
}
查找元素e
int LocateElem(Sqlist* L, Elemtype e)
{
int i;
for(i=0;i<L->length;i++)
{
if(e==L->data[i])
{
return i;
}
}
return 0;
}
或者
int LocateElem(Sqlist* L, Elemtype e)
{
int i=0;
while(i<L->length && L->data[i]!=e)
{
i++;
}
if(i>=L->length) return 0;
else return i+1;
}
插入元素e
注意
需要返回L,所以L加上引用&
bool ListInsert(Sqlist* &L, int i, Elemtype e)
{
int j;
if((i<1) || (i>L->length))
{
return false;
}
i--; //逻辑序号转为物理序号
for(j=L->length;j>i;j--)
{
L->data[j]=L->data[j-1];
}
L->data[i]->e;
L->length++;
return true;
}
删除元素e
bool ListDelete(Sqlist* &L, int i, Elemtype &e)
{
int j;
if((i<1) || (i>L->length))
{
return false;
}
i--;
e=L->data[i];
for(j=i;j<L->length;j++)
{
L->data[j-1]=L->data[j];
}
L->data[length-1]=0;
L->length--;
return true;
}
1.2单链表
节点类型定义
struct LNode
{
Elemtype data;
struct LNode* Next;
};
或者使用typedef:
typedef struct LNode
{
Elemtype data;
struct LNode* Next;
}LNode, *LinkList;
LinkList L 表示单链表头指针L,用于定义单链表;
LNode* P 表示链表内的结点指针P。
显式定义结构体和匿名定义结构体
- 对于顺序表定义,有如下两种:
- 匿名定义结构体
typedef struct
{
Elemtype data[MAXSIZE];
int length;
}Sqlist;
正确性:语法正确,但更符合C语言的风格。
- 显式定义结构体
typedef struct sqlist
{
Elemtype data[MAXSIZE];
int length;
}Sqlist;
正确性:语法正确,但略显冗余。
这两种定义都是正确的,但实际上采用前一种。
顺序表是一个简单的线性结构,通常不需要在结构体内部引用自身(如链表那样),也不需要在其他地方通过 struct Sqlist 标签来引用它。
- 对于链表的定义,有如下两种:
- 匿名定义链表
typedef struct
{
Elemtype data;
struct LNode* Next;
}LNode, *LinkList;
正确性:错误。
注意:由于typedef时没有定义结构体的名称(即这是个匿名结构体),在指针Next
的定义struct LNode* Next;
中,
struct LNode*
并没有被显式定义,所以会导致编译错误。
因为typedef后面的别名LNode是在整个结构体定义之后才生效的,
所以这会导致在结构体内部使用struct LNode*时,编译器并不认识这个类型,从而报错。
- 显式定义链表
typedef struct LNode
{
Elemtype data;
struct LNode* Next;
}LNode, *LinkList;
正确性:正确。
对于像链表这样需要在结构体内部引用自身的数据结构,
通常使用显式标签定义结构体,这样可以方便的在结构体内部引用自身。
- 总结:
- 显式定义结构体名(推荐场景):
- 结构体需要自引用(如链表、树节点):
必须显式命名,以便在内部通过struct Name*引用自身。 - 需要前向声明:
若需在其他地方提前声明结构体(如头文件中),必须显式命名。
- 结构体需要自引用(如链表、树节点):
- 匿名结构体(适用场景):
- 简单结构体,无需自引用或前向声明:
通过typedef简化类型名,使代码更简洁。 - C语言中避免重复写struct关键字:
在C中,匿名结构体配合typedef可省去变量声明时的struct关键字。
- 简单结构体,无需自引用或前向声明:
- 显式定义结构体名(推荐场景):
建立单链表
头插法:结果为L−>ai−>ai−1−>...a1L->a_i->a_{i-1}->...a_1L−>ai−>ai−1−>...a1
void CreateList(LinkList &L, Elemtype a[], int n)
{
LNode* s;
int i;
L=(LNode *)malloc(sizeof(LNode)); //创建头结点
L->next=NULL;
for(i=0;i<n;i++)
{
s=(LNode *)malloc(sizeof(LNode));
s->data=a[i];
s->next=L->next;
L->next=s;
}
}
尾插法:结果为L−>a1−>...−>ai−1−>aiL->a_1->...->a_{i-1}->a_iL−>a1−>...−>ai−1−>ai
void CreateList(LNode* &L, Elemtype a[], int n)
{
LNode *s,*r;
int i;
L=(LNode*)malloc(sizeof(LNode));
r=L; //r指向尾结点
for(i=0;i<n;i++)
{
s=(LNode*)malloc(sizeof(LNode));
s->data=a[i];
r->next=s; //先设置前一个结点的后继结点
r=s; //再移动r保证是尾结点
}
r->next=NULL;
}
初始化单链表
void InitList(LNode* &L) //或者(LinkList L)
{
L=(LNode*)malloc(sizeof(LNode));
L->next=NULL;
}
销毁单链表
void DestroyList(LNode* &L) //or Linklist L
{
//free(s)函数表示释放单个结点
//从头结点开始遍历释放结点,需要两个前后指针;
LNode *pre, *p; //pre是p前驱结点
//p的作用是,当pre在L的尾结点时,p是NULL,表示遍历完成;
*pre=L;
*p=L->next; //初始时
while(p!=NULL)
{
free(pre);
pre=p;
p=pre->next;
}
free(pre); //此时pre指向尾结点,还需要free一次;
}
判断空表
bool ListEmpty(LNode* L) //or LinkList L;
{
return (L->next==NULL);
}
求单链表长度
int ListLength(LNode* L)
{
int n=0;
LNode *p=L; //头结点的序号为0;
while(p->next!=NULL)
{
n++;
p=p->next;
}
return (n); //头结点为空,序号是0,不计入整体长度
}
输出单链表
void DispList(LNode* L)
{
LNode *p=L->next;
while(p!=NULL)
{
cout << p->data << endl;
p=p->next;
}
}
求表中某个元素e
bool GetElem(LNode* L, int i, Elemtype &e)
{
LNode *p=L;
int j=0;
while((j<i) && (p!=NULL))
{
p=p->next;
j++;
}
if(p==NULL)
{
return false;
}
else
{
e=p->data;
return true;
}
}
查找元素e
int LocateElem(LNode* L, Elemtype e)
{
int i=1;
LNode *p=L->next;
while((p!=NULL) && (p->data!=e))
{
p=p->next;
i++;
}
if(p==NULL)
{
return 0;
}
else
{
return i;
}
}
插入元素e
先找i-1,再在它的后面插入。
bool ListInsert(LNode* &L, int i, Elemtype e)
{
int j=0;
LNode *p=L, *s;
while((j<i-1) && (p!=NULL))
{
j++;
p=p->next;
}
if(p==NULL)
{
return false;
}
else
{
s=(LNode*)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
}
删除元素e
先找i-1,再在它的后面删除。
bool ListDelete(LNode* &L, int i, Elemtype &e)
{
int j=0;
LNode *p=L, *q;
while((j<i-1) && (p!=NULL))
{
j++;
p=p->next;
}
if(p==NULL)
{
return false;
}
else
{
q=p->next;
if(q==NULL) return false;
e=q->data;
p->next=q->next;
free(q);
return true;
}
}
单链表逆置
思路:头插法建表,原来的a1插入到最后变成an。
void Reverse(LNode* &L)
{
LNode *p=L->next, *q;
//q是p的后继结点,现在将q调整为p的前置结点
L->next=NULL;
while(p!=NULL)
{
q=p->next;
p->next=L->next;
/*
这里的 L->next 是一个动态变化的值,
每次循环时,L->next 都指向当前已经反转的部分链表的头节点。
把 p->next 赋值为 L->next 的意思,
其实就是把当前节点 p 插入到已经反转的链表的头部。
*/
L->next=p;
/*
先调整p->next使链表反转,再调整L->next使表头更新。
*/
p=q;
}
}
1.3双链表
结点类型定义
typedef struct DNode
{
Elemtype data;
struct DNode* prior;
struct DNode* next;
}DLNode;
建立双链表
头插法建表:
void CreateListF(DLNode* &L, Elemtype a[], int n)
{
DLNode *s;
int i;
L=(DLNode*)malloc(sizeof(DLNode));
L->prior=NULL; L->next=NULL;
for(i=0;i<n;i++)
{
s=(DLNode*)malloc(sizeof(DLNode));
s->data=a[i];
s->next=L->next;
if(L->next!=NULL) L->next->prior=s;
s->prior=L;
L->next=s;
}
}
尾插法建表:
void CreateListR(DLNode* &L, Elemtype a[], int n)
{
DLNode *s, *r; //r始终指向尾结点
int i;
L=(DLNode*)malloc(sizeof(DLNode));
r=L; //开始时r指向头结点
for(i=0;i<n;i++)
{
s=(DLNode*)malloc(sizeof(DLNode));
s->data=a[i];
r->next=s;
s->prior=r;
r=s;
}
r->next=NULL;
}
插入元素e
bool ListInsert(DLNode* &L, int i, Elemtype e)
{
int j=0;
DLNode *p=L, *s;
while((j<i-1) && (p!=NULL))
{
j++;
p=p->next;
}
if(p==NULL)
{
return false;
}
else
{
s=(DLNode*)malloc(sizeof(DLNode));
s->data=e;
s->next=p->next;
if(p->next!=NULL) p->next->prior=s;
s->prior=p;
p->next=s;
return true;
}
}
删除元素e
bool ListDelete(DLNode* &L, int i, Elemtype &e)
{
int j=0;
DLNode *p=L, *q; //q是p的后置结点
while((j<i-1) && (p!=NULL))
{
j++;
p=p->next;
}
if(p==NULL)
{
return false;
}
else
{
q=p->next;
if(q==NULL)
{
return false;
}
e=q->data;
p->next=q->next;
if(p->next!=NULL)
{
p->next->prior=p;
}
free(q);
return true;
}
}
双链表逆置
使用头插法:
void Reverse(DLNode* &L)
{
DLNode *p=L->next, *q;
L->next=NULL;
while(p!=NULL)
{
q=p->next;
L->next=p->next;
if(L->next!=NULL)
{
L->next->prior=p;
}
p->prior=L; //头插法核心
L->next=p;
p=q;
}
}