从零开始的数据结构
本期正式步入数据结构
本篇文章是学习视频中的笔记
参考视频 [浙江大学数据结构(陈越、何钦铭)](数据结构_中国大学MOOC(慕课) (icourse163.org))
线性结构
多项式的表示
多项式的关键数据
1、多项式项数n
2、各项系数ai以及指数i
顺序存储结构直接表示
数组各分量对应多项式各项:
两个多项式相加:两个数组对应分量相加
但是问题是:如何表示多项式x+3x^2000?
显然我们要用2001个分量表示,但其实只有两项是非零的,这样会造成空间的巨大浪费
所以虽然这种方法表示方便,但仍有很大的问题,于是引入第二种方法
顺序存储结构表示非零项
基本思路:只表示非零项
每个非零项 aix^i 涉及两个信息:系数ai和指数i,可以将一个多项式看成是一个(ai,i)二元组的集合
我们这里也可以用数组表示,但每个分量就不能简单只是系数了,而且要包含指数
用结构数组表示:数组分量是由系数ai、指数i组成的结构,对应一个非零项。
这种方法是否计算方便呢?
只要我们按指数大小有序存储,仍然是非常容易的
例如两个多项式相加
先定位到两个多项式的第一项,比较两个定位点对应的项,较大的这一项提取出来,并使该定位点后移,另一个定位点不变,继续比较两个定位点对应的项,循环以上操作,直到其中一个多项式的项全部遍历,然后将另一个多项式剩余项按序排放。
链表结构存储非零项
链表中每个结点存储多项式中的一个非零域,包括系数和指数两个数据域以及一个指针域
排列的时候我们仍然按指数大小有序存储即可
对应的结构定义可以这样写
typedef struct PolyNode *Polynomial:
struct PolyNode{
int coef;
int expon;
Polynomial link;
}
例如仍是上面的P1(x)和P2(x)
线性表
线性表的定义及操作集:
定义:线性表是由同类型数据元素构成有序序列的线性结构
- 表中元素个数称为线性表的长度
- 线性表没有元素时,称为空表
- 表起始位置称表头,结束位置称表尾
类型名称:线性表(List)
数据对象集:线性表是n(>=0)个元素构成的有序序列(a1,a2,… ,an)
操作集:线性表L∈List,整数i表示位置,元素X∈ElementType
- List MakeEmpty() 初始化一个空线性表L
- ElementType FindKth( int K, List L ) 根据位序K返回对应元素
- int Find( ElementType X, List L ) 在线性表L中查找X的第一次出现位置
- void Insert( ElementType X, int i, List L ) 在位序i前插入一个新元素X
- void Delete( int i, List L ) 删除指定位序i的元素
- int Length( List L ) 返回线性表L的长度n
线性表的顺序存储
利用数组的连续存储空间顺序存放线性表的各元素
typedef struct LNode *List;
struct LNode{
ElementType Data[MAXSIZE];
int Last;
};
struct LNode L;
List PtrL;
//访问下标为i的元素:L.Data[i] 或 PtrL->Data[i]
//线性表的长度:L.Last+1 或 PtrL->Last+1
主要操作
-
初始化(建立空顺序表)
List MakeEmpty() { List PtrL; PtrL = (List)malloc(sizeof(struct LNode)); PtrL->Last = -1; return PtrL; }
-
查找
int Find( ElementType X,List PtrL ) { int i = 0; while(i<=PtrL->Last && PtrL->Data[i]!=X) i++; if(i>PtrL->Last) //没找到 return -1; else //找到了 return PtrL; } //查找成功的平均比较次数为(n+1)/2,平均时间性能为O(n)
-
插入( 第 i(1 =< i <= n+1)个位置上插入一个值为X的新元素)
//现移动,再插入 //从后往前挪 void Insert( ElementType X,int i,List PtrL) { int j; if(PtrL->Last == MAXSIZE-1){ printf("表满"); return; } if(i<1||i>PtrL->Last+2){ printf("位置不合法"); return; } for(j = PtrL->Last;j>=i-1;j--) PtrL->Data[j+1]=PtrL->Data[j]; //将ai到an倒序后移 PtrL->Data[i-1] = X; //新元素插入 PtrL->Last++; //Last仍指向最后元素 return; } //平均移动次数为n/2,平均时间性能为O(n)
-
删除
//现移动,再插入 //从后往前挪 void Delete( int i, List L ) { int j; if(i<1||i>PtrL->Last+1){ //检查空表及删除位置的合法性 printf("不存在第%d个元素",i); return; } for(j = i;j<=PtrL->Last;j++) //将a(i+1)到 an 顺序前移 PtrL->Data[j-1]=PtrL->Data[j]; PtrL->Last--;//Last仍指向最后元素 return; } //平均移动次数为(n-1)/2,平均时间性能为O(n)
线性表的链式存储实现
不要求逻辑上相邻的两个元素物理上也相邻;通过“链”建立起数据元素之间的逻辑关系。
- 插入、删除不需要移动数据元素,只需要修改“链”。
typedef struct LNode *List;//定义指向链表指针
struct LNode{
ElementType Data;
List Next;
};
struct Lnode L;
List PtrL;
主要操作
-
求表长
int Length( List PtrL ) { List p = PtrL; //p指向表的第一个结点 int j = 0; while(p){ //条件:p不等于NULL p = p->Mext; j++; } return j; } //时间性能为O(n)
-
查找
(1)按序号查找:FindKth
List FindKth(int K,List PtrL) { List p = PtrL; //p指向表的第一个结点 int i = 1; while(p!=NULL&&i<K) { p=p->Next; i++; } if(i== K) return p; else return NULL; } //平均时间性能O(n)
(2)按值查找:Find
List Find(ElementType X,List PtrL){ List p = PtrL; while(p!=NULL && p->Data != x) p = p->Next; return p; } //平均时间性能O(n)
-
插入( 第 i(1 =< i <= n+1)个位置上插入一个值为X的新元素)
步骤:
- 先构造一个新结点,用s指向
- 再找到链表的第i-1个结点,用p指向
- 修改指针,插入结点(p之后插入新结点是s)
注意顺序(1) s->Next=p->Next;(2) p->Next=s;
List Insert(ElementType X,int i ,List PtrL){ List p,s; //第1个位置插入,表头地址改变 if(i == 1){ s = (List)malloc(sizeof(struct LNode)); s->Data = X; s->Next = PtrL; return s; //返回新的头结点地址 } p = Findkth(i-1,PtrL); //返回第i-1结点地址 //p不存在 if(p == NULL){ printf("参数i错"); return NULL; }else{ s = (List)malloc(sizeof(struct LNode)); s->Data = X; s->Next = p->Next; p->Next = s; return PtrL; //返回头结点地址(头指针不变) } } //平均查找次数n/2
-
删除( 删除第 i(1 =< i <= n)个位置上的结点 )
步骤:
- 先找到链表的第i-1个结点,用p指向;
- 再用指针s指向要被删除的结点(p的下一个结点)
- 然后修改指针,删除s所指结点
- 最后释放s所指结点的空间
List Delete(int i,List PtrL){
List p,s;
if(i == 1){
s = PtrL; //为了释放内存所以保存
if(Ptrl!=NULL)
PtrL = PtrL->Next;
else
return NULL;
free(s);
return PtrL;
}
p = Findkth(i-1,PtrL); //返回第i-1结点地址
//判断指针p的合法性(是否前一个不存在或是结点i不存在)
if(p == NULL){
printf("第%d个结点不存在",i-1);
return NULL;
}else if(p->Next == NULL){
printf("第%d个结点不存在",i);
return NULL;
}else{
s = p->Next;
p->Next = s->Next;
free(s);
return PtrL;
}
}
广义表
引例
我们知道了一元多项式的表示,那么二元多项式如何表示?
比如,给定二元多项式
如何表示呢?
分析:将上述二元多项式看成关于x的一元多项式
所以可以用“复杂”链表表示为:
定义
- 广义表是线性表的推广
- 对于线性表而言,n个元素都是基本的单元素
- 广义表中,这些元素不仅可以是单元素也可以是另一个广义表
typedef struct GNode *GList;
struct GNode{
int Tag; //标志域:0表示结点是单元素,1表示结点是广义表
union{ //子表指针域SubList与单元素数据域Data复用,即共用存储空间
ElementType Data;
GList SubList;
}URegion;
GList Next; //指向后继结点
};
我们在构造时可能会碰到一个问题:一个域有可能是不能分解的单元,有可能是一个指针
C语言有一种手段,union。union可以把不同类型的数据组合在一起,可以把这个空间理解成某种类型,也可以理解为另外一种类型
如何区分不同类型?
一般的方法是设一个标记,即Tag来区分后面的空间是Data还是SubList
多重链表
定义
多重链表:链表中的节点可能同时隶属于多个链
- 多重链表中结点的指针域会有多个,如前面例子包含了Next和SubList两个指针域;
- 但包含两个指针域的链表并不一定是多重链表,比如双向链表不是多重链表
多重链表有广泛用途:基本上树、图这样相对复杂的数据结构都可以采用多重链表方式实现存储
矩阵
矩阵可以用二维数组表示,但二维数组表示有两个缺陷:
- 数组大小需要事先确定
- 对于“稀疏矩阵”,造成大量的存储空间浪费(稀疏矩阵是指矩阵里面0很多)
分析:采用一种典型的多重链表——十字链表来存储稀疏矩阵
-
只存储矩阵非0元素项
结点的数据域:行坐标Row、列坐标Col、数值Value
-
每个结点通过两个指针域,把同行、同列串起来
- 行指针(向右指针)Right
- 列指针(向下指针)Down
头节点的标识值为“Head“,矩阵非0元素结点的标识值为”Term“
对应结构如下:
实例
给出一个矩阵A
链表图如下