——算法、线性表——
概念明晰:随机存取、顺序存取、随机存储和顺序存储
下面完整的介绍一下这4个概念
1、存取结构
分为随机存取和非随机存取(又称顺序存取)
2、存储结构
分为顺序存储和随机存储
3、顺序存储结构
4、随机存储结构
--随机存储最典型的代表为链式存储:
链式存储结构特点
1、比顺序存储结构的存储密度小 (每个节点都由数据域和指针域组成,所以相同空间内假设全存满的话顺序比链式存储更多)。
2、逻辑上相邻的节点物理上不必相邻。
3、插入、删除灵活 (不必移动节点,只要改变节点中的指针)。
4、查找结点时链式存储要比顺序存储慢。
5、每个结点是由数据域和指针域组成
一、数据结构的概念
1、基本概念:

2、算法
(1)概念
(2)重要特性:
3、算法的评价标准(“好”的算法应该考虑达到以下目标)
4、算法的时空效率
(1)时间复杂度
根据算法写成的程序在执行时耗费时间的长度,记为T(n) = O(n)
(2)空间复杂度
根据算法写成的程序在执行时占用存储单元的长度记为S(n)
(3)语句频度
一个算法中的语句执行次数称为语句频度或时间频度,记为T(n)
(4)一般O(n)的计算方法:
(5)常见的时间复杂度有以下七种:
① O(1)常数型;② O(log2N)对数型;③ O(N)线性型;④ O(Nlog2N)二维型;⑤ O(N^2)平方型;⑥ O(N^3)立方型;⑦ O(2^N)指数型。
例如:
i=1;①
while (i<=n)
{
i=i*2; ②
}
解:语句1的频度是1,
设语句2的频度是f(n),则:2^f(n)<=n;f(n)<=log2n
取最大值f(n)= log2n, T(n)=O(log2n )
二、线性表
1、顺序存储
(1)结构体的定义
typedef int Position;
typedef struct LNode * PtrToLNode;
struct LNode
{
ElmenetType Data[ MAXSIZE ];
Position Last;
};
typedef PtrToLNode List;
(2)顺序表的初始化
List MakeEmpty()
{
List L;
L = (List)malloc(sizeof(struct LNode));
L->Last = -1; //Last 置为-1 表示表中没有数据元素
Return L;
}
-
通过L我们可以访问相应线性表的内容。比如:下标为i 的元素:L->Data[i]
-
查询线性表的长度:L->Last+1;
(3)顺序表的查找(时间复杂度为O(n))
#define ERROR -1 /* 将错误信息 ERROR 的值定义为任一负数都可以 */
Position Find( List L, ElementType X )
{
Position i = 0;
While( i <= L->Last && L->Data[i] != X)
i++;
if( i > L->Last)
return ERROR; /* 如果没有找到,则返回错误信息 */
else
return i; /* 找到后返回的是存储位置 */
}
(4)顺序表的插入 (时间复杂度为O(n))
bool Insert( List L, ElementType X, int i)
{ /* 在 L 的指定位序 i 前插入一个新元素 X; 位序 i 元素数组位置下标为 i-1 */
Postion j;
if(L->Last == MAXSIZE-1)
{/* 表空间已满,不能插入 */
printf("表满!\n");
return false;
}
if( i<1 || i > L->Last+2)
{/* 检查插入位序的合法性:是否在 1~n+1; n为当前元素个数,即Last+1 */
printf("位序不合法!\n");
return false;
}
for( j = L->Last; j >= i-1; j--) /*Last 指向序列最后元素an */
L->Data[j+1] = L->Data[j]; /* 将位序为 i 及以后的元素顺序向后移动 */
L->Data[i-1] = X; /* Last 仍指向最后一个元素 */
L->Last++;
return true;
}
(5)顺序表的删除(时间复杂度为O(n))
bool Delete(List L, int i)
{ /*从 L 中删除指定位序 i 的元素,该元素数组下标为 i-1*/
Position j;
if(i < 1 || i > L->Last + 1)/* 检查空表及删除位序的合法性*/
{
printf("位序%d不存在元素",i);
return false;
}
for( j = i; i <= L->Last; j++)
L->Data[j-1] = L->Data[j];/*将位序 i+1 及以后的元素顺序向前移动*/
L->Last--;/*Last 仍指向最后元素*/
return true;
}
2、链表存储
(1)结构体的定义(时间复杂度为O(n))
typedef struct LNode * PtrToLNode;
struct LNode
{
ElementType Data;
PtrToLNode Next;
};
typedef PtrToLNode Position; /*这里的位置是结点的地址 */
typedef PreToLNode List;
(2)求表长(时间复杂度为O(n))
int Length(List L)
{//默认该链表是有头结点的
Position p;
int i=0; /* 初始化计数器 */
//单向链表的遍历(三部曲)
p = L->next; /* p指向表的第 1 个结点 */
while(p)
{ /* 遍历单链表,统计结点数 */
p=p->next;
i++;
}
return i;
}
(3)判空
int ListEmpty(LinkList L)
{ //若 L 为空,则返回1,否侧返回 0
if(L->Next) //非空
return 0;
else
return 1;
}
(4)查找(时间复杂度为O(n))
①按序号查找 FindKth(时间复杂度为O(n))
#define ERROR -1 /* 一般定义为表中元素不可能取到的值 */
ElementType FindKth(List L, int K)
{ /* 根据指定的位序 K, 返回 L 中相应的元素 */
Position P;
int cnt = 1; /* 位序从 1 开始 */
p = L; /* p 指向 L的第 1 个结点 */
while(p && cnt < K)
{
p = p->next;
cnt++
}
if((cnt == K) && p)
return p->Data; /* 返回第 K 个 */
else
return ERROR; /* 否则返回错误信息 */
}
②按值查找,即定位 Find(时间复杂度为O(n))
#define ERROR NULL /*空地址表示错误 */
Position Find( List L, ElementType X)
{
Position p = L;/* p指向 L 的第 1 个元素 */
while(p && p->Data != x)
{
p = p->Next;
}
if(p)
return p;
else
return ERROR;
}
(5)链表的插入(时间复杂度为O(n))


int ListInsert_L(LinkList &L, int i,ElementType e)
{
p = L;
j = 0;
while(p&& j<i-1)
{//寻找第 i-1 个结点
p = p->next;
++=j;
}
if(!p || j > i-1)
return ERROR;//
s = (LinkList)malloc(sizeof(LNode));//生成新结点s
s->data = e; //将结点s 的数据域的值 更新为 e
s->next = p->next; //将结点s 插入 L 中
p->next = s;
return OK;
}
(6)创建链表(时间复杂度为O(n))
1、带头结点的【头插法】(时间复杂度为O(n))

/* 带头结点的插入创建 */
void createListHead( Linklist L, int n )
{
//建立头结点
L = (LNode*)malloc(sizeof(struct LNode));
L->Next = NULL;
//建立单链表(头插法)
LNode *temp = NULL;
//申请空间,写入数据
for(int i = 0; i < n; i++)
{
tmp = (LNode*)malloc(sizeof(struct LNode)); /* 申请、填装结点 */
scanf("%d",&tmp->Data);//输入元素值
//插入到头结点的后面
tmp->Next = L->Next;
L->Next = tmp;
}
}
2、带尾结点的插入【尾插法】(时间复杂度为O(n))

/*带尾结点的插入*/
void CreateList_L( Listlist &L, int n )
{ //正位序数输入 n 个元素的值,建立带表头结点的单链表L
//建立头结点
L = (LNode*)malloc(sizeof(struct LNode));
L->Next = NULL;
//建立单链表(尾插法)
LNode r = L; //尾指针指向头结点
//申请空间,写入数据
for(int i = 0;i < n; i++)
{
LNode *tmp = (LNode*)malloc(sizeof(struct LNode)); /* 申请新结点 */
scanf("%d",&tmp->Data); //输入元素
tmp->Next = NULL;
//插入到尾结点后面
r->next = temp;
r = tmp; //r指向新的尾结点
}
}
(7)删除(时间复杂度为O(n))


//将线性表L 中第 i 个数据元素删除
int ListDelete_L(LinkList &L, int i, ElementType &e)
{
p=L;
int j=0;
while(p->next && j < i-1)
{//寻找第 i 个结点,并令p指向其前驱
p = p>next;
++j;
}
if(!(p->next)||j < i-1)
return ERROR;//删除位置不合理
q = p->next; //临时保存被删除结点的地址以备释放
p->next = q->next; //改变被除结点的驱结点的指针域
e = q->data; //保存被删除结点的数据域
free(q); //释放被删除结点的空间
return OK;
}
3、二者时间复杂度和优缺点的比较
1、两者复杂度比较
| 查找 | 插入 | 删除 | |
|---|---|---|---|
| 顺序表 | O(1) | O(1) | O(n)通过下标直接找到待操作元素,主要时间花在移动元素上。 |
| 链表 | O(n) | O(n)主要时间用于找到插入元素的位置 | O(n)主要时间用于找到待删除元素的位置 |
2、两者优缺点比较
| 数组 | 优点 | 缺点 |
|---|---|---|
| 随机访问性强;查找速度快 | 插入和删除效率低;可能浪费内存;内存空间要求高,必须有足够的连续内存空间;数组大小固定,不能动态拓展 |
| 链表 | 优点 | 缺点 |
|---|---|---|
| 插入删除速度快;内存利用率高,不会浪费内存;大小没有固定,拓展很灵活。 | 不能随机查找,必须从第一个开始遍历,查找效率低 |
三、栈
1、栈的顺序存储实现
(1)顺序栈结构体的定义
typedef int Position;
typedef int ElementType;
typedef struct SNode *PtrToNode;
struct SNode
{
ElementType * Data; /*存储元素的数组*/
Position Top; /*栈顶指针*/
int MaxSize; /*堆栈最大容量*/
};
typedef PtrToNode Stack;
(2)顺序栈的创建
Stack CreateStack(int MaxSize) /*顺序栈的创建*/
{
Stack S = (Stack)malloc(sizeof(struct SNode));
S->Data = (ElementType *)malloc(MaxSize * sizeof(ElementType));
S->Top = -1; /*"-1"表示空栈 "MaxSize-1"表示满栈*/
S->MaxSize = MaxSize; /*指定栈的最大容量*/
return S;
}
(3)判满
bool IsFull(Stack S) /*判断栈是否满了*/
{
return(S->Top == S->MaxSize-1);
}
(4)判空
bool IsEmpty(Stack S) /*判断堆栈是否为空*/
{
return(S->Top == -1);
}
(5)入栈
bool Push(Stack S, ElementType X) /*顺序栈的 入栈 操作*/
{
if(IsFull(S))
{
printf("堆栈满!");
return false;
}
else
{
S->Data[++(S->Top)] = X; /*若是栈不满,则Top加 1,并将新元素放入Data数组的Top位置中*/
return true;
}
}
(6)出栈
ElementType Pop(Stack S) /*顺序栈 的 出栈 操作*/
{
if(IsEmpty(S))
{
printf("堆栈空!");
return ERROR; /*ERROR 是 ElementType 类型的特殊值,标志错误。必须是正常栈元素数据不可能取到的值 */
}
else
return(S->Data[(S->Top)--]); /*若不空,返回Data[Top],同时将Top减 1*/
}
2、栈的顺序存储实现
(1)顺序栈结构体的定义
typedef struct SNode *PtrToSNode;
typedef int ElementType;
struct SNode
{
ElementType Data;
PtrToSNode Next;
};
typedef PtrToSNode Stack;
(2)顺序栈的创建
Stack CreateStack()
{ /*构建一个堆栈的头结点,返回该结点指针*/
Stack S;
S = (Stack)malloc(sizeof(struct SNode));
S->Next = NULL;
return S;
}
(3)判空
bool IsEmpty(Stack S)
{ /*判断堆栈 S 是否为空,若是返回 true,否则返回 false*/
return(S->Next == NULL);
}
(4)判满 注意:链栈,不必判断堆栈是否满
(5)入栈
bool Push(Stack S, ElementType X)
{ /*将元素 X 压入堆栈 S */
PtrToSNode TmpCell;
TmpCell = (PtrToSNode)malloc(sizeof(struct SNode));
TmpCell->Data = X;
//头插法
TmpCell->Next = S->Next;
S->Next =TmpCell;
return true;
}
(6)出栈
ElementType Pop(Stack S) ElementType Pop(Stack S)
{ /*删除并返回堆栈 S 的栈顶元素*/
PtrToSNode FirstCell;
ElementType TopElem;
if(IsEmpty(S))
{
printf("堆栈空!");
return ERROR;
}
else
{
FirstCell = S->Next;
TopElem = FirstCell->Data;
S->Next = FirstCell->Next;
free(FirstCell);
return TopElem;
}
}/*顺序栈 的 出栈 操作*/
{
if(IsEmpty(S))
{
printf("堆栈空!");
return ERROR; /*ERROR 是 ElementType 类型的特殊值,标志错误。必须是正常栈元素数据不可能取到的值 */
}
else
return(S->Data[(S->Top)--]); /*若不空,返回Data[Top],同时将Top减 1*/
}
3、栈的应用
四、队列
1、队列的顺序存储实现
(1) 循环队列的结构体定义
typedef int Status;
typedef int QElemType; /* QElemType类型根据实际情况而定,这里假设为int */
/* 循环队列的顺序存储结构 */
typedef struct QNode
{
QElemType data[MAXSIZE];
int front; /* 头指针 */
int rear; /* 尾指针,若队列不空,指向队列尾元素的下一个位置 */
}SqQueue;
(2)生成空队列
/* 初始化一个空队列Q */
Status CreateQueue(SqQueue *Q)
{
SqQueue *Q = (SqQueue)malloc(sizeof(struct QNode));
Q->data = (ElementType*)malloc(MaxSize * sizeof(ElementType));
Q->front = Q->rear = 0;
return OK;
}
(3)判空
bool IsEmpty(SqQueue *Q)
{
return(Q->front == Q->rear);
}
(4)判满
bool IsFull(SqQueue *Q)
{
return((Q->rear+1)% MaxSize == Q->front);
}
(5)入队
/* 若队列未满,则插入元素e为Q新的队尾元素 */
Status EnQueue(SqQueue *Q,QElemType e)
{
if ((Q->rear+1)%MAXSIZE == Q->front) /* 队列满的判断 */
return ERROR;
Q->data[Q->rear]=e; /* 将元素e赋值给队尾 */
Q->rear=(Q->rear+1)%MAXSIZE;/* rear指针向后移一位置, */
/* 若到最后则转到数组头部 */
return OK;
}
(6)出队
/* 若队列不空,则删除Q中队头元素,用e返回其值 */
Status DeQueue(SqQueue *Q,QElemType *e)
{
if (Q->front == Q->rear) /* 队列空的判断 */
return ERROR;
*e=Q->data[Q->front]; /* 将队头元素赋值给e */
Q->front=(Q->front+1)%MAXSIZE; /* front指针向后移一位置, */
/* 若到最后则转到数组头部 */
return OK;
}
2、队列的链式存储实现
(1)队列的链式存储结构体定义
typedef int Status;
typedef int QElemType; /* QElemType类型根据实际情况而定,这里假设为int */
typedef struct QNode /* 结点结构 */
{
QElemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct /* 队列的链表结构 */
{
QueuePtr front,rear; /* 队头、队尾指针 */
}LinkQueue;
(2)生成空队列
/* 构造一个空队列Q */
Status InitQueue(LinkQueue *Q)
{
Q->front=Q->rear=(QueuePtr)malloc(sizeof(QNode));
if(!Q->front)
exit(OVERFLOW);
Q->front->next=NULL;
return OK;
}
(3)判空
Status QueueEmpty(LinkQueue Q)
{
if(Q.front==Q.rear)
return TRUE;
else
return FALSE;
}
(4)判满 链式队列,不必判断堆栈是否满
(5)入队
/* 插入元素e为Q的新的队尾元素 */
Status EnQueue(LinkQueue *Q,QElemType e)
{
QueuePtr s=(QueuePtr)malloc(sizeof(QNode));
if(!s) /* 存储分配失败 */
exit(OVERFLOW);
s->data=e;
s->next=NULL;
Q->rear->next=s; /* 把拥有元素e的新结点s赋值给原队尾结点的后继,见图中① */
Q->rear=s; /* 把当前的s设置为队尾结点,rear指向s,见图中② */
return OK;
}
(6)出队
/* 若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR */
Status DeQueue(LinkQueue *Q,QElemType *e)
{
QueuePtr p;
if(Q->front==Q->rear)
return ERROR;
p=Q->front->next; /* 将欲删除的队头结点暂存给p,见图中① */
*e=p->data; /* 将欲删除的队头结点的值赋值给e */
Q->front->next=p->next;/* 将原队头结点的后继p->next赋值给头结点后继,见图中② */
if(Q->rear==p) /* 若队头就是队尾,则删除后将rear指向头结点,见图中③ */
Q->rear=Q->front;
free(p);
return OK;
}
五、栈和队列操作的特点
| 相同点 | 不同点 | |
|---|---|---|
| 堆栈(FILO) | 只允许在端点处插入和删除元素; | 栈是先进后出或者后进先出;栈是只能在表的一端进行插入和删除操作的线性表 |
| 队列(FIFO) | 只允许在端点处插入和删除元素; | 队列是先进先出;队列是只能在表的一端进行插入,然后在另外一端进行删除操作的线性表 |
六、数组存储地址的计算
| 数组类型 | 存储地址的计算(a是数组首地址,len是每个数组元素所占长度) |
|---|---|
| 一维数组 | a[i]的存储地址:a+i*len |
| 二维数组:a[m] [n] | 按行存储:a+(i * n+j) * len;按列存储:a+(j * m+i) * len |
例子:数组存储地址的计算示例:
1)已知一维数组a中每个元素占用2个字节,求a[10]的存储地址?
答:a[10]的存储地址为:a+10*2=a+20
2)已知二维数组a[4][5]中, 每个元素占用2个字节,求元素a[3][2]按行为主序存储的存储地址和按列为主序存储的存储地址?
答: 按行存储:a+(35+2) *2 = a+34
按列存储:a+(24+3) *2 = a+22









