!!!Chapter 3 Lists, Stacks, and Queues

本文深入探讨了抽象数据类型(ADT)的概念,包括列表、栈和队列等基本ADT的定义及其不同实现方式,如简单数组、链表、双向链表及循环链表等,并分析了每种实现的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

3.1 Abstract Data Types

An abstract Data Type(ADT) is a set of operations. Abstract data types are mathematical abstractions; nowhere in an ADT's definition is there any mention of how the set of operations is implemented.

Objects such as lists, sets(集合), and graphs,along with their operations, can be viewed as abstract data types, just as integers, reals, and booleans are data types.

The basic idea is that the implementation of there operations is written once in the program, and any other part of the program that needs to perform an operation on the ADT can do so by calling the appropriate function.

3.2 The List ADT

we call the special list of size 0 an empty list.

For any list except the empty list, we say that A(i+1) follows Ai and that A(i-1) precedes Ai. The first element of the list is A1, and the last element is AN. The position of element Ai in a list is i.

A set of operations we perform on list ADT:

Find: returns the position of the first occurrence of a key.

Insert/Delete: insert and delete some key from some position in the list.

FindKth: returns the element in some position.

3.2.1 Simple Array Implementation of Lists

For array, an estimate of the maximum size of the list is required. And we should overestimate, which wastes considerable space.

Array allows PrintList and Find to be carried out in linear time O(N), and the FindKthoperation takes constant timeO(1). However,InsertandDelete are expensive O(N).

Because the running time for insertions and deletions is so slow and the list size must be known in advance, simple arrays are generally not used to implement lists.

3.2.2 Linked Lists

Linked List consists of a series of structures, which are not necessarily adjacent in memory. Each structure contains the element and a pointer to a structure containing its successor. The last cell's Next pointer points to NULL.

PrintList(L) or Find(L,Key)will just traverse the list by following the Next pointers.O(N)

FindKth(L,i) takes O(i) and works by traversing down the list.

Insert and Deletetakes constant timeO(1)

3.2.3 Programming Details

There are two problems unsolved:

1. How to insert/delete at the front.

2. To delete, we must keep track of the cell before the one that we want to delete.

To solve the first problem, we will add a header or dummy node, which is in position 0.

To solve the second problem, we need to write a routine FindPrevious that can return the position of the predecessor.

List ADT routines

Type declarations for linked lists:

#ifndef _List_H

struct Node;
typedef struct Node *PtrToNode;
typedef PtrToNode List;
typedef PtrToNode Position;

List MakeEmpty( List L );
int IsEmpty( List L );
int IsLast( Position P, List L );
Position Find( ElementType X, List L );
void Delete( ElementType X, List L );
Position FindPrevious( ElementType X, List L );
void Insert( ElementType X, List L, Position P );
void DeleteList( List L );
Position Header( List L );
Position First( List L );
Position Advance( Position P );
ElementType Retrieve( Position P );

#endif

// Place in the implementation file
struct Node
{
    ElementType Element;
    Position Next;
};
Test whether a linked list is empty:
int IsEmpty( List L )
{
    return L->Next == NULL;
}
Test whether current position is the last in a linked list:
// Parameter L is unused
int IsLast( Position P, List L )
{
    return p->Next == NULL;
}
Find routineO(N):
// return position of X in L; NULL if not found
Position Find( ElementType X, List L )
{
    Position P;
    P = L->Next;
    while(P!=NULL && P->Element != X)
        P = P->Next;
    return P;
}

FindPrevious routine O(N):

// if x is not found, the next field of returned position is null. Assumes a header
Position FindPrevious( ElementType X, List L)
{
    Position P;
    P = L;
    while( P->Next != Null && P->Next->Element != X)
        P = P->Next;
    return P;
}
Deletion routine O(N):

//delete first occurrence of X, assume header
void Delete( ElementType X, List L)
{
    Position p, TmpCell;
    P = FindPrevious(X,L);
    if(!IsLast(P,L))
    {
        TmpCell = P->Next;
        p->Next = P->Next->Next;
        free(TmpCell);
    }
}
Insertion routine:
// insert after p, assume header, L unused
void Insert( ElementType X, List L, Position P)
{
    Position TmpCell;
    TmpCell = malloc( sizeof(struct Node) );
    if(TmpCell == NULL)
        FatalError( "Out of Space!!!" );
    TmpCell->Element = X;
    TmpCell->Next = P->Next;
    P->Next = TmpCell;    //P should be valid, otherwise, the indirection is illegal
} 

Whenever you do an indirection, you must make sure that the pointer is not NULL.

Delete a list:

void DeleteList ( List L )
{
    Position P, Tmp;
    P = L->Next;
    L->Next = NULL;
    while( P != NULL )
    {
        Tmp = P->Next;
        free( P );
        P = Tmp;
    }
}

3.2.6 Doubly Linked Lists

+ Convenient to traverse lists backwards

+ Don't need to do FindPrevious when delete element

- Add an extra pointer on each struct

- Need to adjust two pointers when insert/delete elements

3.2.6 Circularly Linked Lists

Circularly Linked List has the last cell keep a pointer back to the first. It can be done with or without header, and can be done with doubly linked lists.

3.3 The Stack ADT

3.3.1 Stack Model

A stack is a list with the restriction that insertions and deletions can be performed in only one position, namely, the end of the list, called the top.

The fundamental operations on a stack are Push(insert),Pop(delete) and Top.

Stacks are sometimes known as LIFO(last in, first out) list.

3.3.2 Implementation of Stacks

A stack is a list, any list implementation will do:

Linked List Implementation of Stacks

+ All operations take constant time

- calls to malloc and free are expensive

Type declaration for linked list implementation of stack ADT;

#ifndef _Stack_h

struct Node;
typedef struct Node *PtrToNode;
typedef PtrToNode Stack;

int IsEmpty( Stack S );
Stack CreateStack( void );
void DisposeStack( Stack S );
void MakeEmpty ( Stack S );
void Push ( ElementType X, Stack S );
ElementType Top ( Stack S );
void Pop ( Stack S );

#endif 

// place in implementation file
struct Node
{
    ElementType Element;
    PtrToNode   Next;
};
Routine to create an empty stack
Stack CreateStack( void )
{
    Stack S;
    S = malloc( sizeof(struct Node) );
    if ( S == NULL )
        FatalError( "Out of space!!" );
    MakeEmpty( S );
    return S;
}

void MakeEmpty( Stack S )
{
    if( S == NULL )
        Error("Must use CreateStack first");
    else
        while( !IsEmpty( S ) )
            Pop( S );
}
Routine to push onto a stack:Insert into the front of the list, as we don't know which element is the last one.
void Push (ElementType X, Stack S )
{
    PtrNode TmpCell;
    TmpCell = malloc( sizeof( struct Node) );
    if( TmpCell == NULL )
        FatalError( "Out of space!!" );
    else
    {
        TmpCell->Element = X;
        TmpCell->Next = S->Next;
        S->Next = TmpCell;
    }
}
Routine to return top element in a stack
ElementType Top ( Stack )
{
    if( !IsEmpty( S ) )
        return S->Next->Element;
    Error( "Empty stack" );
    return 0;     //return value used to avoid warning
}
Routine to pop from a stack
void Pop( Stack S )
{
    PtrToNode FirstCell;
    if( IsEmpty( S ) )
        Error( "Empty Stack" );
    else
    {
        FirstCell = S->Next;
        S->Next = S->Next->Next;
        free( FirstCell );
    }
}
Array Implementation of Stacks
- We need to declare an array size ahead of time.
#ifndef _Stack_h
struct StackRecord;
typedef struct StackRecord *Stack;

int IsEmpty( Stack S );
int IsFull( Stack S );
Stack CreateStack( int MaxElements );
void DiSPOSEsTACK( Stack S );
void MakeEmpty( Stack S );
void Push( ElementType X, Stack S );
ElementType Top( Stack S );
void Pop( Stack S );
ElementType TopAndPop( Stack S );

#endif

//place in implementation
#define EmptyTOS( -1 )      //symbol used to define empty stack
#define MinStackSize( 5 )   //self-defined

struct StackRecord
{
    int Capacity;
    int TopOfStack;         //keep track of the top 
    ElementType *Array;
}; 
Stack creation:
Stack CreateStack( int MaxElements )
{
    Stack S;
    if( MaxElements < MinStackSize) 
        Error( "Stack size is too small" );

    S = malloc( sizeof( struct StackRecord ) );
    if(s==NULL)
        FatalError( "Out of Space!!" );

    S->Array = malloc( sizeof( ElementType )*MaxElements );
    if( S->Array == NULL)
        FatalError("Out of Space!!" );
    S->Capacity = MaxElements;
    MakeEmpty( S );       //let TopOfStack points to -1
    return S;
} 
Routine to free stack:
void DisposeStack( Stack S )
{
    if( S!=NULL )
    {
        free( S->Array );
        free( S );
    }
}
Routine to test whether stack is empty
int IsEmpty( Stack S )
{
    return S->TopOfStack == EmptyTOS;
}
Routine to make stack empty:
void MakeEmpty( Stack S )
{
    S->TopOfStack = EmptyTOS;   //don't care about the value
}
Routine to push onto a stack
void Push( ElementType X, Stack S)
{
    if( IsFull( S ) )
        Error( "Full Stack" );
    else
        S->Array[ ++S->TopOfStack] = X;   
}
//priority: suffix > -> > prefix
Routine to return top of stack
ElementType Top( Stack S )
{
    if( !IsEmpty( S ) )
        return S->Array[ S->TopOfStack ];
    Error( "Empty Stack" );
    return 0;
}
Routine to pop from a stack
void Pop( Stack S )
{
    if( IsEmpty( S ) )
        Error( "Empty Stack" );
    else
        S->TopOfStack--;
}

3.4  The Queue ADT

For queue, insertion is done at one end, whereas deletion is performed at the other end.

Basic operations for queue:Enqueue (insert an element at the end of the list),Dequeue (deletes/return the element at the start of the list)

Any list implementation is legal for queues, and every operation takes O(1) running time.

circular array implementation

# ifndef _Queue_h

struct QueueRecord;
typedef struct QueueRecord *Queue;

int IsEmpty( Queue Q );
int IsFull( Queue Q );
Queue CreateQueue( int MaxElements );
void DisposeQueue( Queue Q );
void MakeEmpty( Queue Q );
void Enqueue( ElementType X, Queue Q );
ElementType Front( Queue Q );
void Dequeue( Queue Q );
ElementType FrontAndDequeue( Queue Q );

#endif

// place in the implementation file
struct QueueRecord
{
    int Capacity;
    int Front;
    int Reat;
    int Size;
    ElementType *Array;
};
Routine to make an empty queue:

//when queue is empty Rear = Front - 1, so Array with A elements can only
//store A-1 elements as a queue
void MakeEmpty( Queue Q )
{
    Q->Size = 0;
    Q->Front = 1;
    Q->Rear = 0;
}
Routine to enqueue:

//wraparound when front or rear need to wrap
static int Succ( int Value, Queue Q )
{
    if( ++Value == Q->Capacity )
        Value = 0;
    return Value;
}

void Enqueue( ElementType X, Queue Q )
{
    if( IsFull( Q ) )
        Error( "Full Queue" );
    else
    {
        Q->Size++;
        Q->Rear = Succ(Q->Rear, Q);
        Q->Array[Q->Rear] = X;
    }
}










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值