线性表

顺序表管理结构体 、**顺序表基本操作 **!


一、线性表概念

对于一组拥有 n 个数据元素的线性表,其严格数学定义是:其中任何一个数据元素 a[i]有且仅有一个直接前驱 a[i-1]有且仅有一个直接后继 a[i+1]。首元素 a[0] 无直接前驱,尾元素 a[n-1] 无直接后继。

满足这种数学关系的一组数据,当中的数据是一个挨着一个的,常被称为一对一关系。反之,如果数据之间的关系不是一对一的,就是非线性的。


二、顺序表

1. 基本概念

  • 顺序表:顺序存储的线性表
  • 链式表:链式存储的线性表,简称链表

顺序存储就是将数据存储到一片连续的内存中,在C语言环境下,可以是具名的栈数组(int arr[10] ),或者是匿名的堆数组( calloc( 10 , sizeof(int) ) )。
存储方式不仅仅只是提供数据的存储空间,而是必须要能体现数据之间的逻辑关系。当采用顺序存储的方式来存放数据时,唯一能用来表达数据间本身的逻辑关系的就是存储位置。比如队列中的两个人,小明和小花,如果小明在逻辑上排在相邻的小花的前面,那么在存储位置上也必须把小明存放在相邻的小花的前面。

2. 基本操作

顺序表设计一般而言,为了方便操作顺序表,需要一个专门管理顺序表的”管理结构体“,管理结构体中一般会包含:
a.顺序表总容量,方便判断顺序表是否已满。
b.顺序表当前最末元素下标位置
c.顺序表指针

① 数据类型设计

// 设计数据类型
typedef struct DataType
{
    int Num ;
    char Name[32];
}DataType_t , *P_DataType_t;

② 管理结构体的设计

// 管理结构体设计
typedef struct Cntl
{
    // 总容量
    int Capacity ;

    // 当前最末尾下标
    int LastIndex ;

    // 入口地址 
    DataType_t * EnterPtr ;

} Cntl_t , *P_Cntl_t ;

③ 初始化顺序表

图解
Cntl_t * InitList( unsigned int Capacity )
{

    // 申请管理结构体的内存空间
    Cntl_t * cntl = calloc( 1 , sizeof(Cntl_t) );
    if (cntl == NULL)
    {
        perror("calloc cntl error") ;
        return NULL ;
    }
    

    // 管理结构体初始化
    cntl->Capacity = Capacity ;
    cntl->LastIndex = -1 ;

    // 申请存储数据的堆内存
    cntl->EnterPtr = calloc( Capacity , sizeof( DataType_t ) );
    if (cntl->EnterPtr == NULL)
    {
        perror("calloc Data Arr error") ;
        return NULL ;
    }

    // 返回管理结构体
    return cntl ;
}

④ 有序添加数据

有序插入数据,当前示例默认按照数据的编号进行升序排序:

  • 判断当前顺序表是否已满
  • 从左往右依次比较并寻找第一个比新数据大的节点
  • 找到位置后把当前位置数据及其右边的数据进行移动
  • 为了避免数据被覆盖一般选择自右向左依次右移
  • 移动结束后空出来一个位置用于存储新数据,因此直接存入即可
  • 更新末尾下标并返回当前有效数量
void RightShift( DataType_t * Enter , int EndIndex , int StatrIndex )
{

    for (int i = StatrIndex ; i >= EndIndex ; i-- )
    {
        Enter[i+1] = Enter[i] ;
    }
    
    return ;
}

int OrderlyAdd2List( Cntl_t * cntl , DataType_t * NewData )
{
    // 判断是否已满
    if ( cntl->LastIndex >= cntl->Capacity )
    {
        printf("当前顺序表已满..\n") ;
        return -1 ;
    }
    
    // 寻找合适位置
    int i ;
    for ( i = 0; i <= cntl->LastIndex ; i++)
    {
        // 寻找第一个比新数据大的原始数据
        if ( NewData->Num < (cntl->EnterPtr+i)->Num )
        {
            /* 挪出位置 */
            RightShift( cntl->EnterPtr , i , cntl->LastIndex );
            break; 
        }
        
    }
    
    // 进行插入
    cntl->EnterPtr[i] = *NewData ;

    // 更新末尾下标
    cntl->LastIndex ++ ;

    // 返回当前顺序表的有效数据数量
    return cntl->LastIndex + 1 ;
}

⑤ 遍历显示

void DisplayList( Cntl_t * cntl )
{

    if (cntl->LastIndex < 0)
    {
        printf("顺序表为空..\n");
        return ;
    }


    for (int i = 0; i <= cntl->LastIndex ; i++)
    {
        printf("%d \t %s\n" , 
            (cntl->EnterPtr+i)->Num ,
            (cntl->EnterPtr+i)->Name);
    }
    printf("\n");
    
    return ;
}

⑥ 查找数据(二分法查找)

顺序表最大的优势就是可以使用偏移量实现立即访问,如果数据本身按照某种次序排序,则可以使用二分查找法实现快速搜索。

int DichotomyFind( Cntl_t * cntl , int Num  ,  
                    int LeftIndex , int RightIndex )
{
    int retIndex = -1 ;

    // 退出条件
    if (cntl->EnterPtr == NULL || cntl->LastIndex < 0 ||
            LeftIndex > RightIndex )
    {
        printf("直接退出..\n");
        return -1 ; // -1 表示查找不到
    }

    // 计算得到中间原始的下标
    int Mid = LeftIndex + (RightIndex - LeftIndex)/2 ;

    // 比较并确定寻找的方向
    if ( Num <  (cntl->EnterPtr+Mid)->Num )
    {
        /* 往左边找 */
        retIndex = DichotomyFind( cntl ,  Num  , LeftIndex , Mid-1 );
    }
    else if ( Num >  (cntl->EnterPtr+Mid)->Num)
    {
        /* 往右边找 */
        retIndex = DichotomyFind( cntl ,  Num  , Mid + 1 , RightIndex );

    }
    else{
        /* 找到了 */
        return Mid ;
    }
    
    // 返回结果
    return retIndex ;
}

⑦ 删除数据

删除操作可以在前方的查找功能之后实现,找到目标数据,然后把他从顺序表中抹除即可。

DataType_t * Del2List( Cntl_t * cntl , int DelIndex )
{
    DataType_t * tmp = calloc(1, sizeof(DataType_t)) ;

    if ( cntl->EnterPtr == NULL ||  cntl->LastIndex < 0 || DelIndex < 0  )
    {
        printf("参数异常..\n");
        free(tmp);
        return NULL ;
    }
    
    // 临时存储被剔除的目标数据
    *tmp = cntl->EnterPtr[DelIndex];

    // 左移覆盖
    LeftShift( cntl , DelIndex );

    // 更新当前有效数据量
    cntl->LastIndex -- ;

    return tmp ;
}

⑧ 修改数据

修改数据实际上可以借助查找+删除+有序插入来实现修改,并可以保持数据被修改后依然有序。

void ModifyData( Cntl_t * cntl , int Num )
{

    // 找到目标数据
    int findIndex = DichotomyFind(  
            cntl , Num  , 0 , cntl->LastIndex );
    if (findIndex < 0 )
    {
        printf("找不到目标数据..\n");
        return  ;
    }

    printf("找到目标数据:%d - %s \n" , 
                cntl->EnterPtr[findIndex].Num,
                cntl->EnterPtr[findIndex].Name );

    // 获得新数据
    printf("请输入新的数据:\n");
    DataType_t NewData = GetNewData();

    // 剔除原有数据
    DataType_t * tmp = Del2List(cntl , findIndex );
    free(tmp);

    // 重新插入
    OrderlyAdd2List(cntl , &NewData);

}

⑨ 销毁

销毁实际上是需要把程序中用到所有堆内存进行释放,避免出现内存泄漏
经过分析当前程序中只有两块内存未释放(管理结构体 + 堆数组);

void DestructionList( Cntl_t * cntl )
{
    free(cntl->EnterPtr);
    free(cntl);
}
posted @ 2025-10-27 08:46  林明杰  阅读(11)  评论(0)    收藏  举报